## TypeScript Zod schemas Here are the TypeScript Zod schemas matching your V2 PostgreSQL database definition. These schemas cover: 1. Database Entities: Pure runtime validators representing rows fetched directly from PostgreSQL. 2. AI Payload Extensions: The structured document output stored inside job.ai_metadata. 3. Insert/Create Schemas: Utility types derived with .omit() for creating new records where auto-generated columns (id, created_at, updated_at, etc.) are handled by PostgreSQL defaults. --- ``` import { z } from "zod"; // ========================================== // 1. ATOMIC & REUSABLE SCHEMAS // ========================================== export const UUIDSchema = z.string().uuid(); export const ISODateTimeSchema = z.coerce.date(); export const BoundingBoxSchema = z.object({ ymin: z.number().min(0).max(1000), xmin: z.number().min(0).max(1000), ymax: z.number().min(0).max(1000), xmax: z.number().min(0).max(1000), }); export const BlockTypeSchema = z.enum([ "heading", "paragraph", "table", "margin_note", "signature", "footnote", "header", ]); // ========================================== // 2. PAGE-LEVEL AI METADATA SCHEMA (job_source.ai_metadata) // ========================================== export const TranscribedBlockSchema = z.object({ text: z.string(), confidence: z.number().min(0).max(1), blockType: BlockTypeSchema, boundingBox: BoundingBoxSchema.optional(), }); export const PageAIMetadataSchema = z.object({ detectedLanguage: z.string().optional(), overallConfidence: z.number().min(0).max(1), blocks: z.array(TranscribedBlockSchema), inputTokens: z.number().optional(), outputTokens: z.number().optional(), extractedEntities: z.record(z.string(), z.unknown()).optional(), }); export type PageAIMetadata = z.infer; // ========================================== // 3. TABLE ENTITY SCHEMAS // ========================================== // --- PERSON TABLE --- export const PersonSchema = z.object({ id: UUIDSchema, fullName: z.string().min(1), displayName: z.string().nullable().optional(), maidenName: z.string().nullable().optional(), birthDate: z.string().nullable().optional(), birthDateRaw: z.string().nullable().optional(), birthPlace: z.string().nullable().optional(), deathDate: z.string().nullable().optional(), deathDateRaw: z.string().nullable().optional(), deathPlace: z.string().nullable().optional(), biography: z.string().nullable().optional(), portraitPath: z.string().nullable().optional(), metadata: z.record(z.string(), z.unknown()).default({}), createdAt: ISODateTimeSchema, updatedAt: ISODateTimeSchema, }); // --- DOCUMENT TABLE --- export const DocumentSchema = z.object({ id: UUIDSchema, name: z.string().min(1), documentType: z.string().nullable().optional(), documentDate: z.string().nullable().optional(), documentDateRaw: z.string().nullable().optional(), locationCreated: z.string().nullable().optional(), notes: z.string().nullable().optional(), archiveIdentifier: z.string().nullable().optional(), createdAt: ISODateTimeSchema, updatedAt: ISODateTimeSchema, }); // --- DOCUMENT_PERSON JUNCTION --- export const PersonRoleSchema = z.enum(["author", "recipient"]); export const DocumentPersonSchema = z.object({ id: UUIDSchema, documentId: UUIDSchema, personId: UUIDSchema, role: PersonRoleSchema, createdAt: ISODateTimeSchema, }); // --- JOB TABLE --- export const JobStatusSchema = z.enum([ "queued", "processing", "completed", "partial_success", "failed", ]); export const JobSchema = z.object({ id: UUIDSchema, documentId: UUIDSchema, status: JobStatusSchema.default("queued"), retryCount: z.number().int().nonnegative().default(0), provider: z.string(), model: z.string(), promptName: z.string().nullable().optional(), dateCreated: ISODateTimeSchema, dateUpdated: ISODateTimeSchema, }); // --- SOURCE TABLE --- export const SourceSchema = z.object({ id: UUIDSchema, documentId: UUIDSchema, pageNumber: z.number().int().positive().default(1), uploadName: z.string(), filename: z.string(), filePath: z.string(), rawTranscription: z.string().nullable().optional(), revisedText: z.string().nullable().optional(), dateUploaded: ISODateTimeSchema, dateRevised: ISODateTimeSchema.nullable().optional(), }); // --- JOB_SOURCE JUNCTION (Page Execution Output) --- export const JobSourceStatusSchema = z.enum([ "pending", "transcribed", "failed", ]); export const JobSourceSchema = z.object({ id: UUIDSchema, jobId: UUIDSchema, sourceId: UUIDSchema, status: JobSourceStatusSchema.default("pending"), rawTranscription: z.string().nullable().optional(), aiMetadata: PageAIMetadataSchema.nullable().optional(), rawApiResponse: z.record(z.string(), z.unknown()).nullable().optional(), errorDetail: z.string().nullable().optional(), executedAt: ISODateTimeSchema, }); export type Person = z.infer; export type Document = z.infer; export type DocumentPerson = z.infer; export type Job = z.infer; export type Source = z.infer; export type JobSource = z.infer; ```