import { z } from "zod"; /** Sort directions accepted by the LLM. */ export type SortDirection = "asc" | "desc"; export interface Sort { field: string; direction: SortDirection; } /** * Carte-author-declared shape: which built-in sort fields a query may expose, * and (optionally) which directions are allowed for each. Typically supplied * via `defineParams({ sorts: ... })`. Used by `parsePlan` (to reject * LLM-emitted sorts that don't fit) and `generatePrompt` (to teach the LLM * what's available). */ export interface AllowedSort { /** * Permitted directions for this field. Default `["asc", "desc"]` (both). * Set to a single direction for fields where only one ordering makes * semantic sense (e.g. `createdAt: ["desc"]` for recent-first feeds). */ directions?: SortDirection[]; /** Optional human-readable description shown in the prompt. */ description?: string; } export type AllowedSorts = Record; // ─── Zod schemas ──────────────────────────────────────────────────────────── const sortDirectionSchema = z.enum(["asc", "desc"]); export const sortSchema: z.ZodType = z.object({ field: z.string().min(1), direction: sortDirectionSchema, }); /** * Sugar for `z.array(sortSchema)`. `defineParams({ sorts: ... })` adds this * automatically for built-in sort support. You can still use it directly in a * plain params schema when you want to own sort semantics yourself. */ export const sortsParamSchema = z.array(sortSchema); export const allowedSortSchema: z.ZodType = z.object({ directions: z.array(sortDirectionSchema).min(1).optional(), description: z.string().optional(), }); // ─── Pure validation helper (no drizzle dep) ───────────────────────────────── const DEFAULT_DIRECTIONS: SortDirection[] = ["asc", "desc"]; /** * Returns a human-readable error message if `sort` is not allowed under * `allowed`, or `undefined` if it passes. Used by `validatePlan` and * `applyDrizzleSort`. */ export function checkSort(sort: Sort, allowed: AllowedSorts): string | undefined { const def = allowed[sort.field]; if (!def) { return `field "${sort.field}" is not an allowed sort field`; } const directions = def.directions ?? DEFAULT_DIRECTIONS; if (!directions.includes(sort.direction)) { return `direction "${sort.direction}" is not allowed for field "${sort.field}" (allowed: ${directions.join(", ")})`; } return undefined; }