Passer au contenu principal
Les automations sont les parties d’une app qui s’exécutent seules — selon un calendrier, ou quand un service externe envoie un événement. Vous les déclarez sur un composant process dans kazzle.config.ts. Un composant peut avoir autant de déclencheurs que nécessaire.

La structure

{
  name: 'events',
  type: 'process',
  path: './components/events/index.ts',
  processMode: 'persistent', // ou 'triggered'
  triggers: [
    { name: 'daily-digest', kind: 'schedule', schedule: '0 9 * * *', path: '/cron/daily-digest' },
    { name: 'stripe',       kind: 'webhook',                          path: '/webhook/stripe' },
  ],
}
Deux choses se produisent ici :
  • processMode définit le cycle de vie — serveur long-running, ou exécution unique par déclencheur.
  • triggers[] liste les événements qui doivent déclencher ce composant.
Les deux éléments sont indépendants. Un serveur persistent peut avoir un cron. Un processus éphémère peut avoir un webhook. Choisissez le cycle de vie qui correspond à votre charge de travail, puis attachez autant de déclencheurs que vous le souhaitez.

processMode

ModeCe qui s’exécuteQuand l’utiliser
persistent (par défaut)Un serveur HTTP long-running. Les déclencheurs lui sont POSTés.Le composant sert déjà HTTP, ou maintient un état en mémoire (files d’attente, websockets, caches).
triggeredLe script d’entrée est lancé par déclencheur et se termine.Tâches de fond pures — nettoyage nocturne, gestionnaire webhook Stripe unique, etc. Pas de serveurs inactifs.

Déclencheurs

Chaque déclencheur a un name (unique dans le composant), un kind, et — selon le mode — un schedule et/ou un path.
ChampQuand requisNotes
nametoujoursUtilisé comme segment d’URL webhook et dans les logs. Kebab-case.
kindtoujours'schedule' ou 'webhook'.
schedulequand kind: 'schedule'Expression cron à 5 champs. La résolution à la minute est le minimum.
pathquand processMode: 'persistent'Route HTTP sur votre serveur où le déclencheur arrive.

Mode persistent — HTTP vers le serveur

Quand un déclencheur se déclenche pour un composant persistent, Kazzle POST vers votre serveur au path déclaré. La requête contient :
En-têteCe qu’il vous dit
Authorization: Bearer ${KAZZLE_TRIGGER_SECRET}Validez ceci. Rejetez les appels qui ne correspondent pas.
x-kazzle-trigger-nameLe name du déclencheur du manifeste.
x-kazzle-trigger-run-idID opaque pour la corrélation des logs.
x-kazzle-triggered-bycron | webhook | manual.
Pour les déclencheurs webhook, le corps de la requête d’origine est transféré comme corps POST. Pour les déclencheurs de calendrier, le corps est vide.
// components/events/index.ts (mode persistent)
const TRIGGER_SECRET = process.env.KAZZLE_TRIGGER_SECRET ?? '';

Bun.serve({
  port: Number(process.env.PORT),
  hostname: process.env.HOST,
  async fetch(req) {
    const url = new URL(req.url);

    if (req.method === 'POST' && url.pathname === '/cron/daily-digest') {
      if (req.headers.get('authorization') !== `Bearer ${TRIGGER_SECRET}`) {
        return new Response('Unauthorized', { status: 401 });
      }
      await sendDigest();
      return Response.json({ ok: true });
    }

    if (req.method === 'POST' && url.pathname === '/webhook/stripe') {
      if (req.headers.get('authorization') !== `Bearer ${TRIGGER_SECRET}`) {
        return new Response('Unauthorized', { status: 401 });
      }
      const event = await req.json();
      await handleStripe(event);
      return Response.json({ ok: true });
    }

    return new Response('not found', { status: 404 });
  },
});

Mode triggered — exécution unique par déclencheur

Quand un déclencheur se déclenche pour un composant triggered, Kazzle lance le script d’entrée à neuf et attend sa fermeture. Il n’y a pas de path ; le script apprend quel déclencheur s’est déclenché via des variables d’environnement.
Variable d’envValeur
TRIGGER_NAMELe name du déclencheur du manifeste.
TRIGGERED_BYcron | webhook | manual.
RUN_IDID opaque pour la corrélation des logs.
WEBHOOK_PAYLOADCorps JSON (déclencheurs webhook uniquement).
// components/events/index.ts (mode triggered)
const trigger = process.env.TRIGGER_NAME;
const runId = process.env.RUN_ID;

if (trigger === 'daily-digest') {
  await sendDigest();
} else if (trigger === 'stripe') {
  const event = JSON.parse(process.env.WEBHOOK_PAYLOAD ?? '{}');
  await handleStripe(event);
}

console.log(`run ${runId} done`);
Les composants triggered n’ont pas de machines inactives en production — ils démarrent par appel et s’arrêtent à la fermeture.

URLs des webhooks

POST https://api.kazzle.app/webhooks/{spaceId}/{appId}/{componentName}/{triggerName}
Le segment triggerName doit correspondre à une entrée kind: 'webhook' dans le triggers[] de ce composant. Les noms de déclencheurs inconnus retournent 404.

Résolution du calendrier

Les expressions cron sont à 5 champs (minute, heure, jour du mois, mois, jour de la semaine) et la résolution à la minute est le minimum. Les calendriers sub-minute sont rejetés lors de la validation du manifeste.

Comment les exécutions sont enregistrées

Chaque déclenchement écrit une ligne process_runs avec le trigger_name, triggered_by, run_id, et le statut de sortie de l’exécution. Vous pouvez les interroger depuis votre propre code ou les inspecter dans la vue des exécutions de l’app.

Manque de crédits

Une exécution échouée est enregistrée et loggée, mais le calendrier continue à s’exécuter selon son cadence normal — une exécution défaillante ne désactive jamais le déclencheur. La seule chose qui arrête une exécution est les crédits : chaque déclenchement est vérifié par rapport au solde de l’espace, et tant que l’espace n’a pas de crédits (ou n’a pas de facturation configurée), les exécutions sont ignorées avec un 402. C’est auto-récupérable — le calendrier reste armé et le prochain déclenchement après recharge s’exécute normalement, sans reprise manuelle.

Ajouter des automations plus tard

Une app simple peut commencer sans déclencheurs et en gagner plus tard — ajouter un résumé quotidien, connecter Stripe, exécuter un nettoyage. Le cycle de vie du composant (processMode) et les déclencheurs (triggers[]) sont indépendants, vous pouvez donc les modifier sans réécrire le reste de l’app.