> For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt.

# 中介軟體概覽

中介軟體為排程器加入排程策略與應用程式所需的欄位。模型計算記憶狀態與間隔，chrono 預設把這些間隔轉換為到期時間；中介軟體位於兩者之間，可以調整目標保留率或間隔、管理學習步驟，以及維護模型未曾見過的額外卡片資料。各部分的職責見[整體架構](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/architecture.md)。

:::note 中介軟體邊界
排程器由模型、chrono 與中介軟體組合而成。讓模型只負責記憶狀態更新與間隔計算，讓 chrono 負責時間值與天數運算；中介軟體在核心操作之前提供輸入、在其後調整結果，並承載這兩者未定義的欄位。
:::


For your Agent

實作或說明一個中介軟體策略

複製提示詞

使用目前 ts-fsrs Middleware 契約完成以下任務：[描述任務]。

1. 閱讀 https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/middleware/index.md、https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/middleware/lifecycle.md 和 https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/middleware/built-in.md，了解中介軟體如何擴充排程器。
2. 在定義行為前，先宣告 config、card、revlog 和 cardInitInput 的 schema。
3. 將中介軟體限制為 schema、defaultValue、自訂狀態的 scheduleStatus 宣告以及 review/rollback/nextInterval 處理函式；讓模型負責記憶狀態更新與間隔計算，讓 chrono 負責時間值與天數運算。
4. 恰好呼叫一次 next()。在它之前設定 ctx.candidate.desiredRetention[ctx.input.grade] 等輸入；在它之後讀取或調整計算結果。
5. 復原時可以讀取 ctx.config、ctx.input.card 和 ctx.input.revlog，只寫入 ctx.result.card，並依設定的策略還原複習處理函式儲存的舊值。
6. 不要在處理函式中寫入資料庫、傳送通知或執行其他外部操作；由應用程式儲存最後選定的複習結果。
7. 當內建策略已能涵蓋該行為時，優先重用。
8. 使用目前 public API，並為所寫的處理函式加入聚焦測試。

## 定義欄位與行為

`Middleware` 定義包含必填的 `name`，以及選填的 schema、預設值和處理函式。`defineMiddleware()` 根據 schema 推導處理函式中的型別。可以從 `ts-fsrs` 匯入；內建策略也可從 `ts-fsrs/middlewares` 匯入。

| 定義                                      | 用途                                                                                                                                    |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `schema.config`                         | 驗證傳給 `create({ config })` 的設定。中介軟體共用排程器的設定物件。                                                                                         |
| `schema.card`                           | 宣告並驗證輸入、輸出卡片上的欄位。                                                                                                                     |
| `schema.revlog`                         | 宣告並驗證複習紀錄欄位，包括復原所需的舊值。                                                                                                                |
| `schema.cardInitInput`                  | 宣告 `newCard()` 接受的額外輸入，例如卡片 ID。                                                                                                       |
| `scheduleStatus`                        | 註冊自訂排程狀態，例如 `scheduleStatus: ['buried']`，讓組合後的型別與結果驗證接受這些狀態。                                                                          |
| `handlers.nextInterval`                 | 在 `next()` 前調整 `ctx.candidate.desiredRetention[ctx.input.grade]`，或在其後調整 `ctx.scheduledDays`。複習與直接呼叫 `scheduler.nextInterval()` 時都會執行。 |
| `defaultValue.card`                     | 在建立或重設卡片時提供欄位值，透過 `ctx.operation` 區分這兩種操作。                                                                                            |
| `handlers.review` / `handlers.rollback` | 在呼叫下一個處理函式前後，更新 `ctx.result`。執行方式見[生命週期](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/middleware/lifecycle.md)。       |

Schema 同時提供執行時驗證與 TypeScript 型別推導。以下使用實作了 Standard Schema 的 Zod。在自己的應用程式中執行範例時，除了 ts-fsrs 之外還需要安裝它：


```sh [npm]
npm install zod
```

```sh [yarn]
yarn add zod
```

```sh [pnpm]
pnpm add zod
```

```sh [bun]
bun add zod
```

```sh [deno]
deno add npm:zod
```

## 加入支援復原的連續答對計數

範例中，除了 Again 以外的評分都會讓 `streak` 加一，最多累積到 `streakLimit`；Again 會將它歸零。複習處理函式把原計數存入 revlog，因此即使歸零後復原，也能還原先前的值：

```ts twoslash file="../../../snippets/run-code/middleware.ts" fold height=520
import { dateChrono, defineMiddleware, defineScheduler, Rating } from 'ts-fsrs'
import { FSRS6_DEFAULT_WEIGHTS, FSRS6Model } from 'ts-fsrs/models/fsrs-6'
import { z } from 'zod'

const streakFields = z.object({ streak: z.int().nonnegative() })
const streakMiddleware = defineMiddleware({
  name: Symbol('guide.streak'),
  schema: {
    config: z.object({ streakLimit: z.int().positive() }),
    card: streakFields,
    revlog: streakFields,
  },
  defaultValue: { card: () => ({ streak: 0 }) },
  handlers: {
    review(ctx, next) {
      next()
      const { card, grade } = ctx.input
      ctx.result.revlog.streak = card.streak
      ctx.result.card.streak =
        grade === Rating.Again
          ? 0
          : Math.min(card.streak + 1, ctx.config.streakLimit)
    },
    rollback(ctx, next) {
      next()
      ctx.result.card.streak = ctx.input.revlog.streak
    },
  },
})

const base = defineScheduler({ model: FSRS6Model, chrono: dateChrono })
const scheduler = base.use(streakMiddleware).create({
  config: {
    weights: FSRS6_DEFAULT_WEIGHTS,
    enableShortTerm: false,
    numRelearningSteps: 0,
    streakLimit: 2,
  },
})

const now = new Date('2026-01-01T00:00:00.000Z')
const initial = scheduler.newCard({ now })
const good = scheduler.review({ card: initial, grade: Rating.Good, now })
const again = scheduler.review({
  card: good.card,
  grade: Rating.Again,
  now: good.card.dueAt,
})
const restored = scheduler.rollback(again)
const preview = Array.from(
  scheduler.preview({ card: restored, now: good.card.dueAt })
)

console.log(
  JSON.stringify(
    {
      initial: initial.streak,
      afterGood: good.card.streak,
      afterAgain: again.card.streak,
      savedBeforeAgain: again.revlog.streak,
      restored: restored.streak,
      preview: preview.map(({ card }) => card.streak),
      afterPreview: restored.streak,
    },
    null,
    2
  )
)

```


▶ 執行程式碼

計數從 `0` 增加到 `1`，評為 Again 後回到 `0`，復原後還原為 `1`。預覽中，Again、Hard、Good、Easy 對應的計數分別是 `0、2、2、2`，輸入卡片的計數仍為 `1`。

`base.use(streakMiddleware)` 回傳新的排程器定義，原本的 `base` 不變。需要保留回傳值，或像範例一樣接著呼叫 `.create()`。由於中介軟體透過 `use` 組合，處理函式會以固定順序環繞排程器核心——詳見[生命週期](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/middleware/lifecycle.md)。

現成的策略涵蓋了常見情境，`DefaultScheduler` 已組合了它們。見[內建策略](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/middleware/built-in.md)；直接使用現成排程器見[快速開始](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/quick-start.md)。
