import { z } from "zod/v4"; /** * Event-driven and scheduled triggers (plan ยง11.3). * * Webhook subscriptions ride the existing delivery pipeline * (`app_deliveries`: retry + dead-letter). Schedules dispatch into the * app's logic (hosted or external) without the developer running a * scheduler. */ const webhookEventSchema = z .string() .trim() .regex( /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/, "Webhook events are dotted lowercase identifiers (e.g. `file.created`).", ) .max(160); const cronFieldPattern = "[0-9*,/-]+"; const cronExpressionSchema = z .string() .trim() .regex( new RegExp(`^${cronFieldPattern}( ${cronFieldPattern}){4}$`), "Schedules use 5-field cron expressions.", ) .max(120); export const appScheduleSchema = z.strictObject({ id: z .string() .trim() .regex(/^[a-z][a-z0-9-]*$/, "Schedule ids are lowercase kebab-case.") .max(120), cron: cronExpressionSchema, description: z.string().trim().max(500).optional(), }); export type AppSchedule = z.infer; export const appTriggersV2Schema = z .strictObject({ webhooks: z .array(z.strictObject({ event: webhookEventSchema })) .max(50) .default([]), schedules: z .array(appScheduleSchema) .max(20) .default([]) .refine( (schedules) => new Set(schedules.map((schedule) => schedule.id)).size === schedules.length, "Schedule ids must be unique.", ), }) .default({ webhooks: [], schedules: [] }); export type AppTriggersV2 = z.infer;