Docs / Cron and scheduled jobs
Cron and scheduled jobs
Register UTC schedules, protect internal calls and verify repeated execution.
Deploy the job endpoint first
JoripSpace cron calls a GET or POST endpoint on the deployed Worker. Adding a scheduled() handler or a local timer does not register a project schedule. The sample deletes up to 100 expired app sessions per run.
import { Hono } from 'hono';
import type { AppEnv } from '../types';
export const jobs = new Hono<AppEnv>();
jobs.post('/cleanup-sessions', async (c) => {
// 스케줄러는 비공개 Dispatch 호출로 이 URL을 전달합니다.
// X-JoripSpace-Cron-ID 같은 공개 헤더만으로 허용하면 안 됩니다.
if (new URL(c.req.url).hostname !== 'joripspace-cron.internal')
return c.json({ error: 'scheduler_required' }, 403);
const result = await c.env.DB.prepare(
'DELETE FROM app_sessions WHERE session_hash IN (' +
'SELECT session_hash FROM app_sessions WHERE expires_at<=? ORDER BY expires_at LIMIT 100)'
).bind(Date.now()).run();
// 반복 실행해도 안전하며, 남은 항목은 다음 스케줄에서 처리합니다.
return c.json({ ok: true, deleted: result.meta.changes });
});import { jobs } from './routes/jobs';
app.route('/jobs', jobs);Use the app_sessions table and expiry index from the Hono example. Verify the private internal URL and require platform authentication; a public POST with forged cron headers must return 403.
Check for duplicates and register in UTC
joripspace crons --project PROJECT
joripspace cron create --project PROJECT --schedule "*/5 * * * *" --path /jobs/cleanup-sessions --method POST --name "Clean expired sessions"The five fields are minute, hour, day, month and weekday in UTC. Do not create another schedule when one already serves the same purpose.
| Intent | UTC expression |
|---|---|
| Every five minutes | */5 * * * * |
| Daily at 09:00 KST | 0 0 * * * |
| Daily at midnight KST | 0 15 * * * · 15:00 UTC on the previous day |
Account for daylight-saving changes in other regions.
Run immediately and verify status
joripspace cron run --project PROJECT --cron-id CRON_ID
joripspace crons --project PROJECT
joripspace events --project PROJECT --event-type errorUse the actual CRON_ID from the create response. Check last_status, next_run_at and the intended database change. Registration success is not execution success.
joripspace cron delete --project PROJECT --cron-id CRON_ID --yesDesign for retries, delay and failure
Make every job safe if it runs twice or its response is lost. Use conditional DELETE for cleanup, unique job IDs and database state transitions for billing or delivery, and an outbox with a next_attempt_at index for external API work.
Process bounded batches with cursors instead of reading the entire dataset. Monitor the last successful run and delay of important jobs. Never assume a schedule runs exactly once at the exact registered time.