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, and this page is available as Markdown at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/architecture.md.
  • 繁體中文
  • 架構

    ts-fsrs v6 的排程架構圍繞 Model、Chrono、Middleware 和 Scheduler 組織,將記憶模型、時間處理與排程策略分開,並由 Scheduler 協調執行。

    職責劃分

    組成部分職責
    Model透過 step 更新記憶狀態,透過 nextInterval 計算基礎間隔;FSRS-6 的記憶狀態包含穩定性和難度。
    Chrono定義時間表示與時間運算。dateChrono 使用 JavaScript Date,提供時間差計算和日期加減。
    Middleware加入排程策略,並可擴充 config、card 與 revlog 的 schema,例如目標保留率、學習步驟和最大間隔。
    Scheduler組合 schema 與預設值,驗證設定和輸入,協調 newCard、review、preview 和 rollback 等操作。

    Model 的記憶狀態(如穩定性和難度)不同於 New、Learning 等卡片狀態。卡片的學習狀態與排程策略由 Scheduler 和 Middleware 共同處理。

    應用程式負責選卡、儲存回傳的 card 與 revlog,以及管理使用者和集合。排程器不會自動儲存結果。

    排程器定義與實例

    defineScheduler({ model, chrono }) 建立定義。.use(...) 回傳包含新增 middleware 及其推導型別的新定義,原定義仍可重複使用。.create({ config }) 驗證使用者設定並建立獨立的排程器實例。

    重複使用完整定義

    將包含 .use(...) 組合的定義保留為程式層級預設,在使用者或集合的設定可用時建立實例。

    import { 
    const dateChrono: Chrono<{
        readonly time: SRSSchema<{
            input: Date;
            output: Date;
        }>;
        readonly config: SRSSchema<{
            input: {
                readonly fractionalDays?: boolean | undefined;
            };
            output: {
                readonly fractionalDays: boolean;
            };
        }>;
        readonly fields: {
            readonly card: SRSSchema<{
                input: {
                    dueAt: Date;
                    lastReviewAt?: (Date | null) | undefined;
                };
                output: {
                    dueAt: Date;
                    lastReviewAt: Date | null;
                };
            }>;
            readonly revlog: SRSSchema<{
                input: {
                    dueAt: Date;
                    lastReviewAt?: (Date | null) | undefined;
                    reviewTime: Date;
                };
                output: {
                    dueAt: Date;
                    lastReviewAt: Date | null;
                    reviewTime: Date;
                };
            }>;
        };
    }>
    dateChrono
    ,
    function defineScheduler<const M extends AnyModel, const C extends AnyChrono>(definition: {
        readonly model: M;
        readonly chrono: C;
    }): CustomScheduler<SchedulerNameOf<M>, SchedulerEnvFor<M, C, readonly []>, M, C>
    defineScheduler
    ,
    type Rating = 0 | 1 | 2 | 3 | 4
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    } from 'ts-fsrs'
    import {
    const schedulerDesiredRetentionMiddleware: Middleware<symbol, {
        readonly config: SRSSchema<{
            input: {
                readonly desiredRetention: number | Record<Grade, number>;
            };
            output: {
                readonly desiredRetention: Record<Grade, number>;
            };
        }>;
    }>
    schedulerDesiredRetentionMiddleware
    ,
    const schedulerMaximumIntervalMiddleware: Middleware<symbol, {
        readonly config: SRSSchema<{
            input: {
                readonly maximumInterval?: number;
            };
            output: {
                readonly maximumInterval: number;
            };
        }>;
    }>

    Register after learning steps so explicit steps can keep their exact delay.

    schedulerMaximumIntervalMiddleware
    ,
    } from 'ts-fsrs/middlewares' import {
    const FSRS6_DEFAULT_WEIGHTS: number[]
    FSRS6_DEFAULT_WEIGHTS
    ,
    const FSRS6Model: Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>
    FSRS6Model
    } from 'ts-fsrs/models/fsrs-6'
    // Keep the model, chronology, and policies as a reusable program preset. const
    const schedulerDefinition: CustomScheduler<"fsrs-6", ExtendSchedulerEnv<SchedulerEnvFor<Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
    schedulerDefinition
    =
    defineScheduler<Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>, Chrono<...>>(definition: {
        ...;
    }): CustomScheduler<...>
    defineScheduler
    ({
    model: Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>
    model
    :
    const FSRS6Model: Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>
    FSRS6Model
    ,
    chrono: Chrono<{
        readonly time: SRSSchema<{
            input: Date;
            output: Date;
        }>;
        readonly config: SRSSchema<{
            input: {
                readonly fractionalDays?: boolean | undefined;
            };
            output: {
                readonly fractionalDays: boolean;
            };
        }>;
        readonly fields: {
            readonly card: SRSSchema<{
                input: {
                    dueAt: Date;
                    lastReviewAt?: (Date | null) | undefined;
                };
                output: {
                    dueAt: Date;
                    lastReviewAt: Date | null;
                };
            }>;
            readonly revlog: SRSSchema<{
                input: {
                    dueAt: Date;
                    lastReviewAt?: (Date | null) | undefined;
                    reviewTime: Date;
                };
                output: {
                    dueAt: Date;
                    lastReviewAt: Date | null;
                    reviewTime: Date;
                };
            }>;
        };
    }>
    chrono
    :
    const dateChrono: Chrono<{
        readonly time: SRSSchema<{
            input: Date;
            output: Date;
        }>;
        readonly config: SRSSchema<{
            input: {
                readonly fractionalDays?: boolean | undefined;
            };
            output: {
                readonly fractionalDays: boolean;
            };
        }>;
        readonly fields: {
            readonly card: SRSSchema<{
                input: {
                    dueAt: Date;
                    lastReviewAt?: (Date | null) | undefined;
                };
                output: {
                    dueAt: Date;
                    lastReviewAt: Date | null;
                };
            }>;
            readonly revlog: SRSSchema<{
                input: {
                    dueAt: Date;
                    lastReviewAt?: (Date | null) | undefined;
                    reviewTime: Date;
                };
                output: {
                    dueAt: Date;
                    lastReviewAt: Date | null;
                    reviewTime: Date;
                };
            }>;
        };
    }>
    dateChrono
    ,
    }).
    CustomScheduler<"fsrs-6", SchedulerEnvFor<Model<{ readonly name: "fsrs-6"; readonly config: SRSSchema<{ input: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; output: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; }>; readonly memoryState: SRSSchema<{ input: { stability: number; difficulty: number; }; output: { stability: number; difficulty: number; }; }>; readonly algorithm: FSRS6Algorithm; }>, Chrono<...>, readonly []>, Model<...>, Chrono<...>>.use: <readonly [Middleware<symbol, {
        readonly config: SRSSchema<{
            input: {
                readonly desiredRetention: number | Record<Grade, number>;
            };
            output: {
                readonly desiredRetention: Record<Grade, number>;
            };
        }>;
    }>, Middleware<symbol, {
        readonly config: SRSSchema<{
            input: {
                readonly maximumInterval?: number;
            };
            output: {
                readonly maximumInterval: number;
            };
        }>;
    }>]>(middlewares_0: Middleware<symbol, {
        readonly config: SRSSchema<{
            input: {
                readonly desiredRetention: number | Record<Grade, number>;
            };
            output: {
                readonly desiredRetention: Record<Grade, number>;
            };
        }>;
    }>, middlewares_1: Middleware<...>) => CustomScheduler<...>

    Returns a new scheduler definition extended with the supplied middleware. The current scheduler and any cores created from it remain unchanged.

    use
    (
    const schedulerDesiredRetentionMiddleware: Middleware<symbol, {
        readonly config: SRSSchema<{
            input: {
                readonly desiredRetention: number | Record<Grade, number>;
            };
            output: {
                readonly desiredRetention: Record<Grade, number>;
            };
        }>;
    }>
    schedulerDesiredRetentionMiddleware
    ,
    const schedulerMaximumIntervalMiddleware: Middleware<symbol, {
        readonly config: SRSSchema<{
            input: {
                readonly maximumInterval?: number;
            };
            output: {
                readonly maximumInterval: number;
            };
        }>;
    }>

    Register after learning steps so explicit steps can keep their exact delay.

    schedulerMaximumIntervalMiddleware
    )
    // Supply each user's configuration when creating their scheduler. const
    const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
    scheduler
    =
    const schedulerDefinition: CustomScheduler<"fsrs-6", ExtendSchedulerEnv<SchedulerEnvFor<Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
    schedulerDefinition
    .
    CustomScheduler<"fsrs-6", ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-6"; readonly config: SRSSchema<{ input: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; output: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; }>; readonly memoryState: SRSSchema<{ input: { stability: number; difficulty: number; }; output: { stability: number; difficulty: number; }; }>; readonly algorithm: FSRS6Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>.create: (ctx: {
        readonly config: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        } & {
            readonly fractionalDays?: boolean | undefined;
        } & EmptyPart & {
            readonly desiredRetention: number | Record<Grade, number>;
            readonly maximumInterval?: number | undefined;
        };
    }) => SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
    create
    ({
    config: {
        readonly weights: number[];
        readonly enableShortTerm: boolean;
        readonly numRelearningSteps: number;
    } & {
        readonly fractionalDays?: boolean | undefined;
    } & EmptyPart & {
        readonly desiredRetention: number | Record<Grade, number>;
        readonly maximumInterval?: number | undefined;
    }
    config
    : {
    weights:
    const FSRS6_DEFAULT_WEIGHTS: number[]
    FSRS6_DEFAULT_WEIGHTS
    ,
    enableShortTerm: true, numRelearningSteps: 1, desiredRetention: 0.9, maximumInterval: 1, }, }) const now = new Date('2026-01-01T00:00:00.000Z') const
    const card: {
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        state: State;
        scheduleStatus: "new" | "learning" | "review";
    }
    card
    =
    const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
    scheduler
    .
    SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-6"; readonly config: SRSSchema<{ input: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; output: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; }>; readonly memoryState: SRSSchema<{ input: { stability: number; difficulty: number; }; output: { stability: number; difficulty: number; }; }>; readonly algorithm: FSRS6Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>.newCard: (options?: {
        readonly now?: Date | undefined;
    } | undefined) => {
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        state: State;
        scheduleStatus: "new" | "learning" | "review";
    }
    newCard
    ({ now })
    const
    const result: {
        card: {
            stability: number;
            difficulty: number;
            dueAt: Date;
            lastReviewAt: Date | null;
            state: State;
            scheduleStatus: "new" | "learning" | "review";
        };
        revlog: {
            stability: number;
            difficulty: number;
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
            state: State;
            scheduleStatus: "new" | "learning" | "review";
            rating: Grade;
        };
    }
    result
    =
    const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
    scheduler
    .
    SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-6"; readonly config: SRSSchema<{ input: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; output: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; }>; readonly memoryState: SRSSchema<{ input: { stability: number; difficulty: number; }; output: { stability: number; difficulty: number; }; }>; readonly algorithm: FSRS6Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>.review: (input: {
        readonly card: Readonly<Omit<Readonly<Omit<Readonly<{
            stability: number;
            difficulty: number;
        } & {
            dueAt: Date;
            lastReviewAt: Date | null;
        } & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>;
        readonly grade: Grade;
        readonly now?: Date | undefined;
    }) => {
        ...;
    }
    review
    ({ card, grade:
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Good, now })
    console.log( JSON.stringify( { originalState:
    const card: {
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        state: State;
        scheduleStatus: "new" | "learning" | "review";
    }
    card
    .state,
    state:
    const result: {
        card: {
            stability: number;
            difficulty: number;
            dueAt: Date;
            lastReviewAt: Date | null;
            state: State;
            scheduleStatus: "new" | "learning" | "review";
        };
        revlog: {
            stability: number;
            difficulty: number;
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
            state: State;
            scheduleStatus: "new" | "learning" | "review";
            rating: Grade;
        };
    }
    result
    .
    card: {
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        state: State;
        scheduleStatus: "new" | "learning" | "review";
    }
    card
    .state,
    dueAt:
    const result: {
        card: {
            stability: number;
            difficulty: number;
            dueAt: Date;
            lastReviewAt: Date | null;
            state: State;
            scheduleStatus: "new" | "learning" | "review";
        };
        revlog: {
            stability: number;
            difficulty: number;
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
            state: State;
            scheduleStatus: "new" | "learning" | "review";
            rating: Grade;
        };
    }
    result
    .
    card: {
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        state: State;
        scheduleStatus: "new" | "learning" | "review";
    }
    card
    .dueAt,
    stability:
    const result: {
        card: {
            stability: number;
            difficulty: number;
            dueAt: Date;
            lastReviewAt: Date | null;
            state: State;
            scheduleStatus: "new" | "learning" | "review";
        };
        revlog: {
            stability: number;
            difficulty: number;
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
            state: State;
            scheduleStatus: "new" | "learning" | "review";
            rating: Grade;
        };
    }
    result
    .
    card: {
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        state: State;
        scheduleStatus: "new" | "learning" | "review";
    }
    card
    .stability,
    rating:
    const result: {
        card: {
            stability: number;
            difficulty: number;
            dueAt: Date;
            lastReviewAt: Date | null;
            state: State;
            scheduleStatus: "new" | "learning" | "review";
        };
        revlog: {
            stability: number;
            difficulty: number;
            dueAt: Date;
            lastReviewAt: Date | null;
            reviewTime: Date;
            state: State;
            scheduleStatus: "new" | "learning" | "review";
            rating: Grade;
        };
    }
    result
    .
    revlog: {
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reviewTime: Date;
        state: State;
        scheduleStatus: "new" | "learning" | "review";
        rating: Grade;
    }
    revlog
    .rating,
    }, null, 2 ) )

    範例刻意將最大間隔設為一天。模型仍計算出 2.3065 的穩定性,最大間隔策略將到期時間調整為 1 月 2 日。原卡片保持 New,回傳的卡片為 Review。這個最小預設未加入學習步驟、統計或 fuzzing。

    一次 review 的資料流

    1. 將 card、grade 和 now 傳給 review。排程器驗證輸入,並透過 Chrono 計算經過時間。
    2. Middleware 圍繞核心操作執行。目標保留率 middleware 在計算間隔前提供保留率目標。
    3. 核心操作計算新的記憶狀態,並在需要時呼叫模型計算基礎間隔;Middleware 可以提供或調整最終排程間隔。
    4. 整條鏈返回後,排程器透過 Chrono,根據複習時間和最終間隔計算 dueAt,回傳新的 card 與 revlog。Card 包含新的記憶狀態;revlog 保留複習前的記憶狀態,以及本次評分和複習時間。

    Middleware 順序

    對於 .use(A, B),當各 review handler 都呼叫 next() 時,執行順序為:

    A 的 next() 前 → B 的 next() 前 → 核心操作 → B 的 next() 後 → A 的 next() 後

    next() 前的程式碼準備操作,之後的程式碼讀取下游處理結果。因此註冊順序會影響哪個策略最終生效。範例中,目標保留率在核心操作前設定,最大間隔在其後限制結果。加入學習步驟時,應將最大間隔 middleware 註冊在學習步驟 middleware 之後,學習步驟 middleware 會在返回階段恢復設定的學習間隔,避免它被按天計算的間隔策略覆蓋。

    卡片生命週期見狀態轉換,數學細節見 FSRS-6 公式。