Cron Jobs
Schedule tasks with cron — interval, daily, weekly, cron
Gencow provides a built-in scheduler for cron jobs. Define job handlers as procedure.internal, attach schedules with defineApi({ crons }), and Gencow runs them automatically.
Setup
Add scheduled jobs in gencow/index.ts:
import { cron, defineApi } from "@gencow/core";
import { procedure } from "./runtime";
export const syncData = procedure.internal
.name("data.sync")
.handler(async ({ context: ctx }) => {
// no user session is required; this runs through trusted scheduler transport
});
export const syncDataCron = cron.name("sync-data").interval({ minutes: 30 }).handler(syncData);
export default defineApi({
procedures: { syncData },
crons: { syncDataCron },
});Critical: Cron-only handlers should be
procedure.internal, notprocedure.mutation. Internal procedures are not exposed on/api/mutationand do not generate frontend mutation hooks.
Scheduling Methods
interval — Run Every N Minutes/Hours
export const syncDataCron = cron.name("syncData").interval({ minutes: 30 }).handler(syncData);
// Runs data.sync internal procedure every 30 minutesOptions: { minutes?: number, hours?: number, seconds?: number }
daily — Run Once Per Day
export const morningReportCron = cron.name("morningReport").daily({ hour: 9, minute: 0 }).handler(morningReport);
// Runs reports.daily internal procedure every day at 09:00Options: { hour: number, minute?: number }
weekly — Run Once Per Week
export const weeklyDigestCron = cron.name("weeklyDigest").weekly({ dayOfWeek: 1, hour: 10 }).handler(weeklyDigest);
// Runs reports.weekly internal procedure every Monday at 10:00Options: { dayOfWeek: number (0=Sun, 1=Mon, ..., 6=Sat), hour: number, minute?: number }
cron — Standard Cron Expression
export const cleanupCron = cron.name("customSchedule").cron("*/15 * * * *").handler(cleanupExpired);
// Standard cron: every 15 minutesInline Handlers
Cloud deployments register only named procedure cron jobs in the platform scheduler. Inline async handlers can run in local app runtime paths, but they are skipped from the cloud cron manifest because functions cannot be safely serialized into the platform control plane.
Instead of referencing an internal procedure, you can pass an inline async function:
export const localCleanupCron = cron.name("localCleanup").interval({ minutes: 5 }).handler(async () => {
await removeExpiredLocalFixtures();
});Do not self-fetch your own procedure routes. Shared work belongs in a normal
function that both procedures can call directly. For cloud schedules, wrap that
function in a named procedure.internal and register the named handler.
Complete Example
// gencow/index.ts
import { cron, defineApi } from "@gencow/core";
import { syncNews, generateDailyReport, generateWeeklySummary, cleanupExpired } from "./jobs";
export const syncNewsCron = cron.name("syncNews").interval({ minutes: 30 }).handler(syncNews);
export const dailyReportCron = cron.name("dailyReport").daily({ hour: 9 }).handler(generateDailyReport);
export const weeklySummaryCron = cron
.name("weeklySummary")
.weekly({ dayOfWeek: 1, hour: 10 })
.handler(generateWeeklySummary);
export const cleanupCron = cron.name("cleanup").cron("*/15 * * * *").handler(cleanupExpired);
export default defineApi({
procedures: { syncNews, generateDailyReport, generateWeeklySummary, cleanupExpired },
crons: { syncNewsCron, dailyReportCron, weeklySummaryCron, cleanupCron },
});The Internal Handler
// gencow/news.ts
import { procedure } from "./runtime";
import { news } from "./schema";
const BATCH_LIMIT = 50;
const AI_FANOUT_BUDGET = 4;
export const syncNews = procedure.internal
.name("news.sync")
.handler(async ({ context: ctx }) => {
const response = await fetch("https://api.example.com/news");
const articles = (await response.json()).slice(0, BATCH_LIMIT);
// Avoid unbounded Promise.all over crawling/AI work. Keep a small
// fanout budget and schedule the next batch when there is more work.
for (let i = 0; i < articles.length; i += AI_FANOUT_BUDGET) {
const chunk = articles.slice(i, i + AI_FANOUT_BUDGET);
await Promise.all(chunk.map(async (article) => {
await ctx.db.insert(news).values({
title: article.title,
url: article.url,
fetchedAt: new Date(),
}).onConflictDoNothing();
}));
}
console.log(`Synced ${articles.length} articles`);
});Resource Guardrails
Cron actions run without a user sitting in front of the app, so they need fixed limits:
- Set a batch limit for every crawler, importer, cleanup, and backfill action.
- Set an AI fanout budget before using parallel model calls. Keep concurrency bounded and schedule the next batch with
ctx.scheduler.runAfter()if there is more work. - Avoid unbounded
Promise.all, recursive self-scheduling loops, and loading thousands of rows into memory at once. - Do not expose cron actions as anonymous public mutations. Use
procedure.internalfor scheduler-only work. - Public mutations should not trigger large writes, crawling, or AI fanout without auth, ownership scope, and rate/credit limits.
Cloud Registration
When you run gencow deploy or cloud dev packaging, Gencow registers your
defineApi({ crons }) schedules with the platform scheduler. Job identities and
transport authentication are managed by the platform and rotate with deploys.
Legacy gencow/crons.ts and cronJobs() schedules are still read during
migration, but new apps should use independent cron defs in
defineApi({ crons }) with procedure.internal handlers.
Viewing Cron Status
- Dashboard: Admin Dashboard → Scheduler tab
- Logs: Cron execution results appear in
gencow devconsole output
Cloud Deployment
When you run gencow deploy or cloud dev packaging, the CLI includes
dependency-free schedule metadata in the deploy bundle. The platform scheduler
uses that metadata to run your registered procedure.internal handlers without
importing your TypeScript file in the control-plane process.
Important Notes
- Register independent
crondefs withdefineApi({ crons }). - Cron-only handlers should be
procedure.internal; user-callable wrappers should use separateprocedure.mutationnames. - Inline async functions are local-runtime only and are skipped from cloud manifests.
- All times are in server timezone (UTC in cloud deployments)
- Cron jobs run even when no users are connected
Next Steps
- Deployment — Deploy your app with cron jobs
- AI Engine — Add AI capabilities