Tâches Cron
Vue d’ensemble
Section intitulée « Vue d’ensemble »Rebase inclut un planificateur de tâches cron intégré pour exécuter des tâches d’arrière-plan récurrentes : nettoyage de données, génération de rapports, vérifications de santé (health checks), synchronisations avec des API externes, et bien plus encore.
Les tâches cron suivent le même modèle de découverte basée sur les fichiers que les fonctions personnalisées : déposez un fichier TypeScript dans votre répertoire crons/, et Rebase l’enregistre et le planifie automatiquement.
- Zéro dépendance — Aucune bibliothèque externe de planification requise
- API d’administration — Points de terminaison REST pour lister, déclencher, activer/désactiver et consulter les journaux
- Tableau de bord Studio — Surveillez toutes les tâches, consultez l’historique d’exécution et déclenchez des exécutions manuellement
- Persistance en base de données — Journaux d’exécution stockés dans PostgreSQL, survivant aux redémarrages
- Cache en mémoire — Mémoire tampon circulaire rapide (50 dernières exécutions) pour le tableau de bord, adossée à la base de données
Définir une tâche cron
Section intitulée « Définir une tâche cron »Créez un fichier dans votre répertoire backend/crons/ qui exporte par défaut une définition cron. Utilisez le helper defineCron d’@rebasepro/server pour l’inférence de types et l’autocomplétion :
// backend/crons/health-check.tsimport { defineCron } from "@rebasepro/server";
export default defineCron({ schedule: "*/5 * * * *", // every 5 minutes name: "System Health Check", description: "Monitors uptime and memory usage",
async handler(ctx) { ctx.log("Running health check...");
const uptime = process.uptime(); const mem = process.memoryUsage();
ctx.log(`Uptime: ${Math.round(uptime)}s`); ctx.log(`Heap: ${Math.round(mem.heapUsed / 1024 / 1024)}MB`);
return { uptimeSeconds: Math.round(uptime), heapUsedMB: Math.round(mem.heapUsed / 1024 / 1024), }; },});rebase dev surveille le répertoire des crons, ainsi une tâche ajoutée pendant son exécution est
enregistrée au rechargement suivant — sans redémarrage. (Il doit être notifié : le répertoire est
scanné plutôt qu’importé, le watcher ne peut donc pas l’inférer.)
Le nom de fichier (sans extension) devient l’identifiant unique de la tâche — par exemple, health-check.
Configuration
Section intitulée « Configuration »Activez les tâches cron en ajoutant cronsDir à la configuration de votre backend :
const instance = await initializeRebaseBackend({ // ... other config functionsDir: path.resolve(__dirname, "../functions"), cronsDir: path.resolve(__dirname, "../crons"), // ← add this});C’est tout. Rebase va :
- Scanner le répertoire à la recherche de fichiers
.ts/.js - Enregistrer chaque export par défaut en tant que tâche cron
- Créer automatiquement les tables
rebase.cron_logs,rebase.cron_claimsetrebase.cron_job_statedans PostgreSQL (si le pilote prend en charge le SQL) - Démarrer le planificateur et initialiser les compteurs à partir des journaux existants en base de données
- Monter les routes REST d’administration sur
/api/admin/cron
Les tâches cron comme valeurs
Section intitulée « Les tâches cron comme valeurs »Depuis 0.25 En 0.24, les tâches cron ne proviennent que d’un répertoire.
crons prend les tâches sous la forme d’un enregistrement (record) qui associe l’identifiant de chaque tâche à sa définition, à côté de collections. Un répertoire n’est qu’une façon de le construire : loadCrons(dir) en lit un et produit exactement cette forme.
import path from "path";import { defineCron, initializeRebaseBackend, loadCrons } from "@rebasepro/server";
const nightlyReport = defineCron({ schedule: "0 3 * * *", async handler({ log }) { log("building the report"); }});
await initializeRebaseBackend({ app, server, crons: { "nightly-report": nightlyReport } });// the same as cronsDir:await initializeRebaseBackend({ app, server, crons: await loadCrons(path.resolve(__dirname, "../crons")) });Donnez crons ou cronsDir, pas les deux : le démarrage refuse la paire. Construire le backend enregistre les tâches ; ce qui lance leurs timers, c’est start(), que initializeRebaseBackend appelle pour vous. Avec createRebaseBackend, vous l’appelez vous-même, dans le processus qui doit les exécuter. loadCronJobsFromDirectory et loadCronJobsWithDiagnostics fonctionnent toujours, sont dépréciées et avertissent une seule fois.
Syntaxe de planification
Section intitulée « Syntaxe de planification »Les expressions cron utilisent le format standard à 5 champs :
┌───────────── minute (0–59)│ ┌─────────── hour (0–23)│ │ ┌───────── day of month (1–31)│ │ │ ┌─────── month (1–12)│ │ │ │ ┌───── day of week (0–6, Sunday = 0)│ │ │ │ │* * * * *| Expression | Signification |
|---|---|
* * * * * |
Chaque minute |
0 * * * * |
Chaque heure |
0 3 * * * |
Tous les jours à 3h00 |
0 0 * * 1 |
Chaque lundi à minuit |
0 9 1 * * |
Le premier jour de chaque mois à 9h00 |
0,30 * * * * |
Toutes les 30 minutes (à :00 et :30) |
0 9-17 * * 1-5 |
Toutes les heures, de 9h à 17h, du lundi au vendredi uniquement |
Les pas (*/n), les plages (a-b) et les listes (a,b,c) sont tous pris en charge.
Une planification est comparée à l’heure locale de son fuseau. Lors d’un changement d’heure, cela signifie qu’une tâche à heure fixe dans l’heure répétée (30 2 * * * là où les horloges reculent à 03:00) s’exécute lors des deux passages de cette heure, et qu’une tâche dans l’heure sautée, quand les horloges avancent, ne s’exécute pas ce jour-là.
Référence de CronJobDefinition
Section intitulée « Référence de CronJobDefinition »timezone est nouveau — sur la version 0.17.3, une planification est toujours interprétée dans le
fuseau horaire propre de l’hôte. Tout le reste sur cette interface est déjà déployé.
interface CronJobDefinition { // Cron schedule expression (5-field format) schedule: string;
// IANA zone the schedule is read in, e.g. "Europe/Madrid". Without it the // schedule is read in the host's own zone — UTC in nearly every container, // yours on a laptop — so name it. An unknown zone is refused when the job // loads rather than read as local time. timezone?: string;
// Human-readable name shown in Studio name: string;
// Optional description shown in Studio description?: string;
// Whether the job runs (default: true). A pause or a resume from Studio // or the admin API overrides it, for every process, until reset. enabled?: boolean;
// Max execution time in seconds (default: 300). Infinity means no // timeout; 0, a negative number or NaN is refused when the job loads. timeoutSeconds?: number;
// How far back to look on startup for a slot that elapsed while no // instance was ticking (default: off). See "Cron across instances". catchUpWindowSeconds?: number;
// The function to run on each tick handler: (ctx: CronJobContext) => Promise<unknown> | unknown;}Contexte du gestionnaire (Handler Context)
Section intitulée « Contexte du gestionnaire (Handler Context) »Chaque gestionnaire reçoit un CronJobContext contenant des méthodes utilitaires et l’instance du client Rebase :
interface CronJobContext { // The job's unique ID (derived from filename) jobId: string;
// The scheduled tick timestamp scheduledAt: Date;
// Logger — lines appear in Studio, the logs API and the server log log: (...args: unknown[]) => void;
// Aborted when the run exceeds `timeoutSeconds`, or when a shutdown's // wait for it runs out signal: AbortSignal;
// The server-side Rebase singleton — the same object `import { rebase } // from "@rebasepro/server"` returns, and the same one `defineFunction` // hands its callback. rebase: RebaseServerClient;}Utilisez ctx.log() pour émettre une sortie structurée. Ces lignes sont capturées dans le journal d’exécution et visibles dans Studio ainsi que via l’API REST. Chaque ligne est aussi écrite dans le journal du serveur au niveau info, avec un champ cron contenant l’ID du job : elle apparaît donc partout où la sortie de votre conteneur est collectée, y compris dans les journaux de débogage de Rebase Cloud. Cette copie est masquée comme les lignes de journal du serveur lui-même : les valeurs de clés comme password ou apiKey, ainsi que le SQL et les paramètres d’une requête en échec, sont remplacés. Le journal d’exécution conserve la ligne sans ce masquage.
Depuis 0.25 Le journal d’exécution conserve les 500 premières lignes d’une exécution ou 64 Ko d’entre elles, selon ce qui survient en premier, puis une ligne indiquant combien d’autres il y en avait : … 9500 more lines not kept — see the server log. Le journal du serveur reçoit toutes les lignes. Une tâche qui journalise une ligne par élément traité doit s’attendre à ce que son historique montre le début de l’exécution.
ctx.signal — interrompre le travail lorsque l’exécution s’arrête
Section intitulée « ctx.signal — interrompre le travail lorsque l’exécution s’arrête »Le timeout met fin à l’exécution : le planificateur cesse d’attendre et enregistre un échec.
Il n’arrête pas le gestionnaire. Passez ctx.signal à tout ce qui en accepte un, et
le travail s’arrêtera avec lui :
export default defineCron({ name: "Sync inventory", schedule: "*/15 * * * *", timeoutSeconds: 60, async handler({ signal, log }) { const res = await fetch("https://supplier.example.com/stock", { signal }); log(`fetched ${res.status}`); }});Sans cela, une tâche dont le timeout correspond à son intervalle laisse fuiter une requête abandonnée à chaque déclenchement — invisible, car chaque exécution est déjà enregistrée comme échouée.
Interagir avec la base de données et les services via ctx.rebase
Section intitulée « Interagir avec la base de données et les services via ctx.rebase »ctx.rebase.dataAsAdmin est le plan de données au périmètre administrateur. Un cron ne disposant
d’aucun utilisateur par requête, il n’existe pas d’alternative au périmètre utilisateur ici — appliquez
vous-même les filtres de périmètre sur chaque requête.
// backend/crons/expire-users.tsimport { defineCron } from "@rebasepro/server";
export default defineCron({ schedule: "0 0 * * *", // Daily at midnight name: "Expire Inactive Accounts",
async handler(ctx) { ctx.log("Checking for expired trial users...");
// Fetch using the pre-initialized data driver. `collection<Row>(slug)` // gives the query builder the row type — `where` keys are checked // against it. Every filter is an `[operator, value]` tuple; a bare // value is passed straight through and builds a malformed query. const users = ctx.rebase.dataAsAdmin.collection<{ id: string; email: string; trial_status: string; trial_ends_at: string; status: string; }>("users");
const { data: trials } = await users.find({ where: { trial_status: ["==", "active"], trial_ends_at: ["<", new Date().toISOString()] } });
ctx.log(`Found ${trials.length} users with expired trials.`);
for (const user of trials) { await users.update(user.id, { trial_status: "expired", status: "disabled" });
// Send email notification using the Rebase email service await ctx.rebase.email.send({ to: user.email, subject: "Your trial has expired", html: "<p>Please upgrade your subscription to continue.</p>" }); } }});API REST
Section intitulée « API REST »Lister, déclencher, suspendre et reprendre les tâches par HTTP, et client.cron du SDK typé : API des tâches cron.
Tableau de bord Studio
Section intitulée « Tableau de bord Studio »Lorsque des tâches cron sont configurées, un outil Cron Jobs apparaît dans Rebase Studio sous la section Compute, à côté de la console JS. Le tableau de bord propose :
- Liste des tâches — Toutes les tâches enregistrées avec des indicateurs d’état en direct
- Panneau de détails — Planification, prochaine/dernière exécution, durée et informations sur les erreurs
- Historique d’exécution — Entrées de journal extensibles avec les sorties capturées et les résultats
- Déclenchement manuel — Exécutez n’importe quelle tâche à la demande en un clic. Le panneau attend la fin de l’exécution et indique comment elle s’est déroulée : une exécution qui a échoué affiche son erreur
- Activer/Désactiver — Mettez en pause et reprenez les tâches sans redémarrer le serveur, pour tous les processus à la fois ; une pause est conservée après les redémarrages et les déploiements
- Tâches introuvables — chaque tâche que le planificateur a refusée, avec sa planification et la raison, et le nombre de fichiers cron qui n’ont pas pu être chargés. Aucune d’elles n’est planifiée ; voir Les tâches introuvables
Le tableau de bord s’actualise automatiquement toutes les 15 secondes.
Le panneau affiche la même chose quel que soit le processus qui le sert. Une
tâche qu’un autre processus exécute apparaît comme en cours.
Sur chaque processus qui dispose d’un magasin, le nombre d’exécutions, le nombre d’échecs et la dernière exécution sont lus dans la base de données, où sont comptées les exécutions de tous les processus. Les compteurs propres d’un processus ne contiennent que les exécutions qu’il a faites : le rôle api à côté d’un worker n’exécute rien, et de deux répliques qui planifient toutes deux, aucune ne voit les créneaux que l’autre a réservés. Ce n’est que lorsque la base de données ne peut pas être lue que la carte se rabat sur les compteurs propres de ce processus, avec un avertissement dans le journal du serveur.
Depuis 0.25 Les compteurs couvrent toute la durée de vie ; l’historique couvre les 30 derniers jours. La ligne de chaque exécution dans rebase.cron_logs est supprimée 30 jours après son début, par un balayage horaire, tandis que les compteurs d’exécutions et d’échecs continuent de la compter. Une exécution ignorée compte comme une exécution, comme toujours : c’est une ligne de l’historique.
Validation des planifications et parsing AST
Section intitulée « Validation des planifications et parsing AST »Lors de l’initialisation du backend, Rebase analyse toutes les planifications cron enregistrées à l’aide d’un expandeur cron en JS sans dépendance externe :
- Vérification de syntaxe : Vérifie que la chaîne contient exactement 5 champs séparés par des espaces (
minute,hour,day of month,month,day of week). - Développement des plages : Déconstruit les pas (
*/15), les plages (9-17) et les listes séparées par des virgules (0,30) en tableaux explicites d’entiers valides associés à leurs limites respectives (ex. minutes0-59, heures0-23, mois1-12). - Si une expression cron échoue à la validation, Rebase rejette la définition, consigne une erreur au démarrage et refuse d’enregistrer la tâche afin d’éviter les échecs d’exécution au runtime.
Sous le capot : Correction de la dérive de l’horloge (Clock-Drift)
Section intitulée « Sous le capot : Correction de la dérive de l’horloge (Clock-Drift) »Les planificateurs standards basés sur des intervalles (tels que setInterval) dérivent au fil du temps et provoquent d’importants pics d’utilisation CPU en raison des délais d’ordonnancement de la boucle d’événements au niveau de l’OS. Pour garantir la précision d’exécution, Rebase implémente une boucle de calcul dynamique de l’heure cible :
- Calcul du candidat : Dès la fin d’une tâche ou au démarrage du planificateur, Rebase calcule le timestamp exact de la prochaine minute candidate correspondante.
- Sommeil dynamique : Il calcule la différence en millisecondes (
nextRun.getTime() - now.getTime()) et planifie un uniquesetTimeout. - Seuil de sécurité anti-dérive : Un tampon de sommeil minimal (
MIN_SCHEDULE_INTERVAL_MS) de 5 000 ms est appliqué. Si un cycle du planificateur se termine extrêmement rapidement, ce seuil empêche un double déclenchement quasi instantané. - Fermeture propre : Les identifiants de timers sont explicitement détachés de la boucle d’événements Node.js via
timer.unref(), garantissant que les planificateurs cron en arrière-plan ne bloquent pas l’arrêt propre des processus lors des déploiements.
Plus d’un processus
Section intitulée « Plus d’un processus »Chaque processus dont le planificateur est actif arme les mêmes minuteurs ; la base de données décide lequel d’entre eux exécute chaque créneau. Comment un créneau ne s’exécute qu’une fois, comment un créneau perdu lors d’un redémarrage est rattrapé, comment une pause atteint chaque réplique et pourquoi un déclenchement manuel ne s’exécute jamais à côté d’une exécution planifiée est décrit dans Cron entre plusieurs instances.
Délais d’expiration (Timeouts) et isolation des erreurs
Section intitulée « Délais d’expiration (Timeouts) et isolation des erreurs »- Course de timeout forcé : Les blocs d’exécution sont enveloppés dans un
Promise.raceface à un timer d’expiration dérivé detimeoutSeconds(par défaut :300secondes / 5 minutes ;Infinitypour aucun timeout). Si le gestionnaire reste bloqué au-delà de ce seuil,ctx.signalest annulé et la promesse est rejetée, levant l’erreur :Error: Cron job "<id>" timed out after <N>msL’annulation est la partie qui interrompt le travail ; le rejet empêche seulement le planificateur de continuer à attendre. Un gestionnaire qui ignorectx.signalcontinue de tourner au-delà de sa propre exécution. - Arrêt :
backend.shutdown()attend une exécution en cours, dans le même budget que la file de jobs (les deux tiers du timeout d’arrêt). Si l’exécution tourne encore quand il est épuisé,ctx.signalest annulé et elle est enregistrée comme échouée, avec la raison. Son créneau reste réservé, donc aucune autre instance ne la réexécute. - Try/Catch sécurisé : Chaque gestionnaire de tâche s’exécute au sein d’une enveloppe isolée. Toute exception non interceptée est capturée, formatant la trace d’erreur en une chaîne, définissant le statut de la tâche sur
"error"et mettant à jour le compteur d’échecs de la tâche. Un plantage au sein d’une tâche cron individuelle ne fera jamais planter la boucle du planificateur ni le serveur web HTTP principal sous Hono. - Mémoire tampon circulaire en mémoire : Le planificateur maintient un ring buffer contenant les 50 dernières exécutions par tâche. Cette mémoire tampon est conservée en mémoire vive pour permettre des lectures quasi instantanées depuis Rebase Studio.
Schéma de persistance en base de données
Section intitulée « Schéma de persistance en base de données »Lorsque des adaptateurs de base de données prenant en charge le SQL (ex. PostgreSQL) sont actifs, Rebase configure la table rebase.cron_logs :
CREATE SCHEMA IF NOT EXISTS rebase;
CREATE TABLE IF NOT EXISTS rebase.cron_logs ( id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text, job_id TEXT NOT NULL, started_at TIMESTAMPTZ NOT NULL, finished_at TIMESTAMPTZ NOT NULL, duration_ms INTEGER NOT NULL, success BOOLEAN NOT NULL DEFAULT true, error TEXT, -- Stack trace or error message result JSONB, -- Return value of handler logs JSONB, -- Ring buffer array of ctx.log outputs manual BOOLEAN NOT NULL DEFAULT false -- True if triggered from Studio/REST);
ALTER TABLE rebase.cron_logs ADD COLUMN IF NOT EXISTS counted BOOLEAN; -- the run is in the counters below
CREATE INDEX IF NOT EXISTS idx_cron_logs_job ON rebase.cron_logs(job_id, started_at DESC);CREATE INDEX IF NOT EXISTS idx_cron_logs_started ON rebase.cron_logs(started_at);CREATE INDEX IF NOT EXISTS idx_cron_logs_uncounted ON rebase.cron_logs(job_id, started_at DESC) WHERE counted IS NULL;
CREATE TABLE IF NOT EXISTS rebase.cron_job_state ( job_id TEXT PRIMARY KEY, enabled BOOLEAN, -- NULL: follow the job's own `enabled` updated_at TIMESTAMPTZ, -- when `enabled` was last set, and by whom updated_by TEXT, running_until TIMESTAMPTZ, -- the run lease: live while in the future running_by TEXT, total_runs BIGINT NOT NULL DEFAULT 0, -- all-time, skips included total_failures BIGINT NOT NULL DEFAULT 0, last_run_at TIMESTAMPTZ, -- the most recently started run last_duration_ms INTEGER, last_success BOOLEAN, last_error TEXT);Chacune est créée au démarrage si elle manque, de sorte qu’une base de données
antérieure l’obtient à son prochain déploiement. Aucune n’est une collection, et
le rôle d’utilisateur final rebase_user n’a aucun privilège sur elles : un
cron_job_state accessible en écriture permettrait à un utilisateur connecté de
mettre une tâche en pause pour tout le monde, ou de retenir son bail pour que
rien ne l’exécute.
Depuis 0.25 La ligne d’une exécution et son décompte sont écrits en une seule instruction : l’insertion dans cron_logs et un upsert des compteurs de la tâche dans cron_job_state. Une liste lit une ligne de cron_job_state par tâche, quelle que soit la longueur de l’historique, et le planificateur fait de même lorsqu’il initialise totalRuns et totalFailures au démarrage. Les écritures de journal ne retardent pas l’exécution ; si l’une échoue, le planificateur enregistre l’erreur et poursuit, l’exécution restant dans la mémoire tampon circulaire en mémoire.
Une fois par heure, et au démarrage, le planificateur supprime les lignes d’historique de plus de 30 jours et compte toute exécution pas encore comptée, le tout par lots de 5 000 lignes. Un historique écrit avant la 0.24 est compté par le premier de ces balayages, hors du chemin de démarrage. Tant qu’il n’est pas terminé, une liste ajoute aux compteurs les exécutions pas encore comptées, de sorte que les chiffres ne changent pas à la mise à niveau. Il en va de même pour une exécution qu’un ancien serveur journalise pendant un déploiement progressif.
Exemple : Tâche de nettoyage quotidien
Section intitulée « Exemple : Tâche de nettoyage quotidien »// backend/crons/cleanup-sessions.tsimport type { CronJobDefinition } from "@rebasepro/types";import { rebase } from "@rebasepro/server";
const job: CronJobDefinition = { schedule: "0 3 * * *", // daily at 3 AM name: "Cleanup Expired Sessions", description: "Removes user sessions older than 30 days",
async handler(ctx) { ctx.log("Starting session cleanup...");
// Admin-scoped data access — see `ctx.rebase` above. const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(); const expired = await ctx.rebase.dataAsAdmin.sessions.findAll({ where: { last_seen_at: ["<", cutoff] } }); for (const session of expired) { await ctx.rebase.dataAsAdmin.sessions.delete(session.id as string); }
ctx.log(`Cleaned up ${expired.length} expired sessions`);
return { deletedSessions: expired.length }; },};
export default job;Les crons dans le graphe de ressources
Section intitulée « Les crons dans le graphe de ressources »Chaque fichier cron constitue également une déclaration. rebase resources le liste sous le
nom du fichier — le même identifiant sous lequel le planificateur l’exécute et que Studio affiche —
avec sa planification et son fuseau horaire, de sorte qu’un hôte lit les planifications d’un projet avant de
lancer quoi que ce soit. Un cron ne dépend d’aucune variable d’environnement ; rebase status
l’affiche en vert sans rien à configurer.
Lire la planification implique d’importer le fichier, et rebase resources est une étape
de build : aucun .env, aucun secret. Veillez donc à ce que la portée du module d’un cron ne contienne rien
qui lise la configuration au moment de l’import — un client de base de données initialisé au début d’un
helper, un fichier env.ts qui valide DATABASE_URL. Importez plutôt ce code à l’intérieur du
gestionnaire :
async handler({ log }) { const { runSeed } = await import("../src/seed.js"); await runSeed(); log("done");}Le gestionnaire s’exécute au sein du déploiement, là où ces variables existent. Un import de premier
niveau de ce même module rend le graphe calculable uniquement sur une machine disposant
par hasard d’un fichier .env — et cela charge l’intégralité de la dépendance à chaque démarrage qui
se contente d’enregistrer la tâche.
Depuis 0.25 Charger un cron ne
déclare rien. Une étape de build qui vous appartient et qui veut des crons dans le graphe
le dit explicitement, avec declareCrons :
import { declareCrons, loadCrons } from "@rebasepro/server";
declareCrons(await loadCrons("./crons", { onSkip: (problem) => console.warn(problem) }));Prochaines étapes
Section intitulée « Prochaines étapes »- Vue d’ensemble du Backend — Référence complète de la configuration du backend
- Callbacks d’entités — Exécuter de la logique lors des modifications de données
- Intégration de Webhooks — Envoyer des notifications sur des événements
- Cron entre plusieurs instances — Une exécution par créneau, créneaux manqués, pause partout et exécutions qui se chevauchent