> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kazzle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Автоматизація

> Планування завдань та обробка вебхуків всередині додатку Kazzle.

Автоматизація — це частини додатку, які працюють самостійно — за розкладом або коли зовнішній сервіс надсилає подію.

Ви оголошуєте їх на компоненті process у `kazzle.config.ts`. Один компонент може мати стільки тригерів, скільки вам потрібно.

## Структура

```ts theme={"theme":"material-theme-darker"}
{
  name: 'events',
  type: 'process',
  path: './components/events/index.ts',
  processMode: 'persistent', // або 'triggered'
  triggers: [
    { name: 'daily-digest', kind: 'schedule', schedule: '0 9 * * *', path: '/cron/daily-digest' },
    { name: 'stripe',       kind: 'webhook',                          path: '/webhook/stripe' },
  ],
}
```

Тут відбуваються дві речі:

* `processMode` визначає **життєвий цикл** — довгоживучий сервер або одноразовий запуск на тригер.
* `triggers[]` перелічує **події**, які мають запустити цей компонент.

Ці два елементи незалежні один від одного. Постійний сервер може мати крон. Тимчасовий процес може мати вебхук. Виберіть життєвий цикл, який підходить для вашого завдання, а потім додайте стільки тригерів, скільки вам потрібно.

## `processMode`

| Режим                           | Що запускається                                           | Коли використовувати                                                                                |
| ------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `persistent` (за замовчуванням) | Довгоживучий HTTP-сервер. Тригери надсилаються в нього.   | Компонент уже обслуговує HTTP або зберігає стан у пам'яті (черги, вебсокети, кеші).                 |
| `triggered`                     | Скрипт входу запускається на кожен тригер і завершується. | Чисті фонові завдання — нічна очистка, обробник одного вебхука Stripe тощо. Без неробочих серверів. |

## Тригери

Кожен тригер має `name` (унікальне в межах компонента), `kind` та — залежно від режиму — `schedule` та/або `path`.

| Поле       | Коли обов'язково                 | Примітки                                                        |
| ---------- | -------------------------------- | --------------------------------------------------------------- |
| `name`     | завжди                           | Використовується як сегмент URL вебхука та в логах. Kebab-case. |
| `kind`     | завжди                           | `'schedule'` або `'webhook'`.                                   |
| `schedule` | коли `kind: 'schedule'`          | 5-польовий крон-вираз. Мінутна роздільна здатність — мінімум.   |
| `path`     | коли `processMode: 'persistent'` | HTTP-маршрут на вашому сервері, де приземляється тригер.        |

## Режим Persistent — HTTP у сервер

Коли тригер спрацьовує для постійного компонента, Kazzle надсилає POST на ваш сервер за оголошеним `path`. Запит містить:

| Заголовок                                        | Що він вам говорить                                 |
| ------------------------------------------------ | --------------------------------------------------- |
| `Authorization: Bearer ${KAZZLE_TRIGGER_SECRET}` | Перевірте це. Відхиліть виклики, які не збігаються. |
| `x-kazzle-trigger-name`                          | `name` тригера з маніфесту.                         |
| `x-kazzle-trigger-run-id`                        | Непрозорий ID для кореляції логів.                  |
| `x-kazzle-triggered-by`                          | `cron` \| `webhook` \| `manual`.                    |

Для вебхук-тригерів тіло оригінального запиту пересилається як тіло POST. Для тригерів розкладу тіло порожнє.

```ts theme={"theme":"material-theme-darker"}
// components/events/index.ts (режим 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 });
  },
});
```

## Режим Triggered — одноразово на тригер

Коли тригер спрацьовує для компонента `triggered`, Kazzle запускає скрипт входу заново та чекає його завершення. Немає `path`; скрипт дізнається, який тригер спрацював, з змінних середовища.

| Змінна середовища | Значення                                |
| ----------------- | --------------------------------------- |
| `TRIGGER_NAME`    | `name` тригера з маніфесту.             |
| `TRIGGERED_BY`    | `cron` \| `webhook` \| `manual`.        |
| `RUN_ID`          | Непрозорий ID для кореляції логів.      |
| `WEBHOOK_PAYLOAD` | Тіло JSON (тільки для вебхук-тригерів). |

```ts theme={"theme":"material-theme-darker"}
// components/events/index.ts (режим 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`);
```

Компоненти Triggered не мають неробочих машин на продакшені — вони запускаються на кожен виклик і завершуються при виході.

## URL вебхуків

```text theme={"theme":"material-theme-darker"}
POST https://api.kazzle.app/webhooks/{spaceId}/{appId}/{componentName}/{triggerName}
```

Сегмент `triggerName` має збігатися з записом `kind: 'webhook'` у `triggers[]` цього компонента. Невідомі імена тригерів повертають 404.

## Роздільна здатність розкладу

Крон-вирази мають 5 полів (хвилина, година, день місяця, місяць, день тижня) і мінутна роздільна здатність — мінімум. Розклади з роздільною здатністю менше хвилини відхиляються під час перевірки маніфесту.

## Як записуються запуски

Кожне спрацювання тригера записує рядок `process_runs` з `trigger_name`, `triggered_by`, `run_id` та статусом завершення запуску. Ви можете запитати їх з вашого коду або переглянути їх у представленні запусків додатку.

## Вичерпання кредитів

Невдалий запуск записується та логується, але розклад продовжує працювати за звичайним графіком — ненадійний запуск ніколи не вимикає тригер. Єдине, що зупиняє запуск — це кредити: кожне спрацювання тригера перевіряється проти балансу простору, і поки простір без кредитів (або не має налаштованого біллінгу), запуски пропускаються з `402`. Це самовідновлюється — розклад залишається активним і наступне спрацювання після поповнення запускається нормально, без ручного відновлення.

## Додавання автоматизації пізніше

Простий додаток може почати без тригерів і отримати їх пізніше — додайте щоденне резюме, підключіть Stripe, запустіть очистку. Життєвий цикл компонента (`processMode`) та тригери (`triggers[]`) незалежні, тому ви можете змінити їх без переписування решти додатку.
