> ## 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 앱 내에서 작업을 예약하고 웹훅 이벤트에 응답합니다.

자동화는 앱의 일부로, 일정에 따라 또는 외부 서비스에서 이벤트를 보낼 때 자동으로 실행됩니다.

`kazzle.config.ts`의 process 컴포넌트에서 선언합니다. 하나의 컴포넌트는 필요한 만큼 많은 트리거를 가질 수 있습니다.

## 구조

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

두 가지가 일어나고 있습니다:

* `processMode`는 **생명주기**를 선택합니다 — 장시간 실행되는 서버 또는 트리거당 일회성 실행.
* `triggers[]`는 이 컴포넌트를 실행해야 하는 **이벤트**를 나열합니다.

두 부분은 독립적입니다. 지속적인 서버는 cron을 가질 수 있습니다. 임시 프로세스는 웹훅을 가질 수 있습니다. 워크로드에 맞는 생명주기를 선택한 후 원하는 만큼 많은 트리거를 연결하세요.

## `processMode`

| 모드                 | 실행 내용                           | 사용 시기                                              |
| ------------------ | ------------------------------- | -------------------------------------------------- |
| `persistent` (기본값) | 장시간 실행되는 HTTP 서버. 트리거가 POST됩니다. | 컴포넌트가 이미 HTTP를 제공하거나 메모리에 상태를 유지합니다 (큐, 웹소켓, 캐시).  |
| `triggered`        | 진입 스크립트가 트리거당 생성되고 종료됩니다.       | 순수 백그라운드 작업 — 야간 정리, 단일 Stripe 웹훅 핸들러 등. 유휴 서버 없음. |

## 트리거

각 트리거는 `name` (컴포넌트 내에서 고유), `kind`, 그리고 모드에 따라 `schedule` 및/또는 `path`를 가집니다.

| 필드         | 필수 여부                          | 참고                                    |
| ---------- | ------------------------------ | ------------------------------------- |
| `name`     | 항상                             | 웹훅 URL 세그먼트 및 로그에서 사용됩니다. Kebab-case. |
| `kind`     | 항상                             | `'schedule'` 또는 `'webhook'`.          |
| `schedule` | `kind: 'schedule'`일 때          | 5필드 cron 표현식. 분 단위 해상도가 최소입니다.        |
| `path`     | `processMode: 'persistent'`일 때 | 트리거가 도착하는 서버의 HTTP 경로.                |

## 지속 모드 — 서버로의 HTTP

지속 컴포넌트의 트리거가 실행되면, Kazzle은 선언된 `path`에서 서버로 POST합니다. 요청은 다음을 포함합니다:

| 헤더                                               | 의미                               |
| ------------------------------------------------ | -------------------------------- |
| `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 mode)
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` 컴포넌트의 트리거가 실행되면, 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 mode)
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`);
```

트리거 컴포넌트는 프로덕션에서 유휴 머신이 없습니다 — 호출당 시작되고 종료 시 종료됩니다.

## 웹훅 URL

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

`triggerName` 세그먼트는 해당 컴포넌트의 `triggers[]`에서 `kind: 'webhook'` 항목과 일치해야 합니다. 알 수 없는 트리거 이름은 404를 반환합니다.

## 일정 해상도

Cron 표현식은 5필드 (분, 시간, 월의 날짜, 월, 요일)이며 분 단위 해상도가 최소입니다. 분 미만의 일정은 매니페스트 검증 시 거부됩니다.

## 실행 기록 방식

각 트리거 실행은 `trigger_name`, `triggered_by`, `run_id` 및 실행의 종료 상태와 함께 `process_runs` 행을 작성합니다. 자신의 코드에서 이를 쿼리하거나 앱의 실행 보기에서 검사할 수 있습니다.

## 크레딧 부족

실패한 실행은 기록되고 로깅되지만, 일정은 정상 주기에 따라 계속 실행됩니다 — 불안정한 실행이 트리거를 비활성화하지 않습니다. 실행을 중지하는 유일한 것은 크레딧입니다: 모든 트리거 실행은 스페이스의 잔액에 대해 확인되며, 스페이스가 크레딧 부족 상태이거나 청구 설정이 없는 동안 실행은 `402`로 건너뜁니다. 이는 자동 복구됩니다 — 일정은 활성 상태로 유지되고 충전 후 다음 실행은 수동 재개 없이 정상적으로 실행됩니다.

## 나중에 자동화 추가

간단한 앱은 트리거 없이 시작하고 나중에 추가할 수 있습니다 — 일일 요약 추가, Stripe 연결, 정리 실행. 컴포넌트의 생명주기 (`processMode`)와 트리거 (`triggers[]`)는 독립적이므로 앱의 나머지 부분을 다시 작성하지 않고도 변경할 수 있습니다.
