> ## 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 應用程式內排程工作並回應 Webhook。

自動化是應用程式中自行執行的部分 — 按照排程執行，或當外部服務傳送事件時執行。

您在 `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。臨時程序可以有 webhook。選擇適合工作負載的生命週期，然後附加任意數量的觸發器。

## `processMode`

| 模式               | 執行內容                          | 何時使用                                        |
| ---------------- | ----------------------------- | ------------------------------------------- |
| `persistent`（預設） | 長期執行的 HTTP 伺服器。觸發器被 POST 到其中。 | 元件已經提供 HTTP 服務，或在記憶體中保持狀態（佇列、websocket、快取）。 |
| `triggered`      | 每個觸發器都會產生入口指令碼並退出。            | 純背景工作 — 夜間清理、單一 Stripe webhook 處理器等。無閒置伺服器。 |

## 觸發器

每個觸發器都有一個 `name`（在元件內唯一）、一個 `kind`，以及 — 根據模式 — 一個 `schedule` 和/或 `path`。

| 欄位         | 何時必需                            | 備註                                  |
| ---------- | ------------------------------- | ----------------------------------- |
| `name`     | 總是                              | 用作 webhook URL 段和日誌中。使用 kebab-case。 |
| `kind`     | 總是                              | `'schedule'` 或 `'webhook'`。         |
| `schedule` | 當 `kind: 'schedule'` 時          | 5 欄位 cron 表達式。分鐘解析度是最低限度。           |
| `path`     | 當 `processMode: 'persistent'` 時 | 觸發器到達的伺服器上的 HTTP 路由。                |

## 持久模式 — 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`。 |

對於 webhook 觸發器，原始請求本文會作為 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 本文（僅限 webhook 觸發器）。         |

```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`);
```

觸發元件在生產環境中沒有閒置機器 — 它們按呼叫產生並在退出時關閉。

## Webhook URL

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

`triggerName` 段必須與該元件 `triggers[]` 中的 `kind: 'webhook'` 項目相符。未知的觸發器名稱會傳回 404。

## 排程解析度

Cron 表達式是 5 欄位（分鐘、小時、月份日期、月份、週份日期），分鐘解析度是最低限度。子分鐘排程在清單驗證時被拒絕。

## 如何記錄執行

每個觸發器觸發都會寫入一個 `process_runs` 列，包含 `trigger_name`、`triggered_by`、`run_id` 和執行的退出狀態。您可以從自己的程式碼查詢這些，或在應用程式的執行檢視中檢查它們。

## 信用額度用盡

失敗的執行會被記錄和記載，但排程會按其正常節奏繼續執行 — 不穩定的執行永遠不會停用觸發器。停止執行的唯一因素是信用額度：每個觸發器觸發都會根據空間的餘額進行檢查，當空間信用額度用盡（或未設定帳單）時，執行會以 `402` 跳過。這是自我恢復的 — 排程保持啟用，補充後的下一次觸發會正常執行，無需手動恢復。

## 稍後新增自動化

簡單的應用程式可以不使用觸發器開始，稍後再新增它們 — 新增每日摘要、連接 Stripe、執行清理。元件的生命週期（`processMode`）和觸發器（`triggers[]`）是獨立的，因此您可以在不重寫應用程式其餘部分的情況下變更它們。
