> ## 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 не имеют простаивающих машин в production — они запускаются при каждом вызове и завершаются при выходе.

## 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[]`) независимы, поэтому вы можете изменять их без переписывания остальной части приложения.
