For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-CN/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-CN/llms-full.txt, and this page is available as Markdown at https://open-spaced-repetition.github.io/ts-fsrs/zh-CN/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 公式。