#迁移到 v6 与 FSRS-7
包版本与模型版本是两件事:ts-fsrs v5 已使用 FSRS-6。先将应用代码和存储记录迁移到 v6 API,继续使用 FSRS-6;再单独迁移模型。本文对应本站的 v6 API,安装时应锁定与本文接口一致的 v6 版本。
使用 defineScheduler 和明确的模型入口。迁移后的代码不得使用 fsrs()、FSRS、createEmptyCard、generatorParameters、旧调度器类和策略,或根入口导出的旧 Card、CardInput、ReviewLog、FSRSParameters 等 legacy 类型。部分符号仍为兼容而导出,不代表它们是 v6 的接入方式。下文旧名称仅用于静态对照;可运行示例不使用 legacy API 或类型。
复制给有仓库访问权限的 Agent:先确认迁移目标,再梳理变更 TODO List;用户确认清单后才实施与验证。
#1. 迁移到 v6 API,保持 FSRS-6
#定义调度策略
defineScheduler({ model, chrono }) 创建可复用的定义;.use(...) 组合中间件并返回新定义;.create({ config }) 校验配置并创建独立调度器实例。将定义保留在程序层级,根据用户或牌组配置创建实例。
裸定义不包含全部 v5 调度策略。下面沿用当前标准预设的中间件顺序:目标保留率、模糊、统计、已调度天数、学习步骤、最大间隔、单调间隔。请保留实际配置和 FSRS-6 权重。使用 dateChrono 时,fractionalDays: false 按 UTC 日历日计算间隔;仅迁移 API 时不要未经验证就同时改变时间策略。
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 , type State = 0 | 1 | 2 | 3
const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State } 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 schedulerFuzzingMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>
schedulerFuzzingMiddleware ,
const schedulerLearningStepsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval?: number;
readonly allowModelOverride?: boolean;
};
output: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval: number;
readonly allowModelOverride: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
}>
schedulerLearningStepsMiddleware ,
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 ,
const schedulerMonotonicIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after fuzzing and learning steps, when present.
schedulerMonotonicIntervalMiddleware ,
const schedulerScheduledDaysMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly fractionalDays?: boolean | undefined;
};
output: {
readonly fractionalDays: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
}>
Register before interval middleware so it records their final value on unwind.
schedulerScheduledDaysMiddleware ,
const schedulerStatsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly clearStatsOnForget?: boolean | undefined;
};
output: {
readonly clearStatsOnForget: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly reps: number;
readonly lapses: number;
};
output: {
readonly reps: number;
readonly lapses: number;
};
}>;
}>
schedulerStatsMiddleware ,
} from 'ts-fsrs/middlewares'
import { 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 , const migrateFSRS6Parameters: (parameters?: readonly number[]) => number[] migrateFSRS6Parameters } from 'ts-fsrs/models/fsrs-6'
// Keep the definition at application scope; create an instance per config.
export const const definition: 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<...>>
definition = 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 enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>, ... 4 more ..., Middleware<...>]>(middlewares_0: Middleware<...>, middlewares_1: Middleware<...>, middlewares_2: Middleware<...>, middlewares_3: Middleware<...>, middlewares_4: Middleware<...>, middlewares_5: Middleware<...>, middlewares_6: 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 schedulerFuzzingMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>
schedulerFuzzingMiddleware ,
const schedulerStatsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly clearStatsOnForget?: boolean | undefined;
};
output: {
readonly clearStatsOnForget: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly reps: number;
readonly lapses: number;
};
output: {
readonly reps: number;
readonly lapses: number;
};
}>;
}>
schedulerStatsMiddleware ,
const schedulerScheduledDaysMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly fractionalDays?: boolean | undefined;
};
output: {
readonly fractionalDays: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
}>
Register before interval middleware so it records their final value on unwind.
schedulerScheduledDaysMiddleware ,
const schedulerLearningStepsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval?: number;
readonly allowModelOverride?: boolean;
};
output: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval: number;
readonly allowModelOverride: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
}>
schedulerLearningStepsMiddleware ,
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 ,
const schedulerMonotonicIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after fuzzing and learning steps, when present.
schedulerMonotonicIntervalMiddleware
)
export 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 definition: 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<...>>
definition .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: Omit<SchedulerConfigInput<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 []>, "fractionalDays" | ... 8 more ... | "clearStatsOnForget"> & {
...;
};
}) => SchedulerCore<...>
create ({
config: Omit<SchedulerConfigInput<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 []>, "fractionalDays" | ... 8 more ... | "clearStatsOnForget"> & {
...;
}
config : {
weights: function migrateFSRS6Parameters(parameters?: readonly number[]): number[] migrateFSRS6Parameters (), // Pass the user's existing FSRS-6 weights here.
fractionalDays: false,
enableShortTerm: true,
numRelearningSteps: 1, // Keep equal to relearningSteps.length.
desiredRetention: 0.9,
learningSteps: ['1m', '10m'],
relearningSteps: ['10m'],
enableFuzz: false,
maximumInterval: 36500,
clearStatsOnForget: false, // Match v5 forget's default counter behavior.
},
})
export type type Card = {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card = ReturnType<typeof 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?: (Omit<{
readonly now?: Date | undefined;
} & {
readonly cardId?: string | number | undefined;
}, "now"> & {
readonly now?: Date | undefined;
}) | undefined) => {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
newCard >
export type type Revlog = {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
Revlog = ReturnType<typeof 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">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep?: number | undefined;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>;
readonly grade: Grade;
readonly now?: Date | undefined;
}) => {
...;
}
review >['revlog']
// An application's stored v5 record, not a legacy library type.
type type StoredV5Card = {
due: string;
last_review?: string | null;
stability: number;
difficulty: number;
state: State;
scheduled_days: number;
learning_steps: number;
reps: number;
lapses: number;
}
StoredV5Card = {
due: string
last_review?: string | null
stability: number
difficulty: number
state: type State = 0 | 1 | 2 | 3 State
scheduled_days: number
learning_steps: number
reps: number
lapses: number
}
export function function convertCard(stored: StoredV5Card, cardId: Card["cardId"]): Card convertCard (
stored: type StoredV5Card = {
due: string;
last_review?: string | null;
stability: number;
difficulty: number;
state: State;
scheduled_days: number;
learning_steps: number;
reps: number;
lapses: number;
}
StoredV5Card ,
cardId: type Card = {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card ['cardId']
): type Card = {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card {
const scheduleStatus = {
[const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .New]: 'new',
[const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .Learning]: 'learning',
[const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .Review]: 'review',
[const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .Relearning]: 'learning',
} as const
return const definition: 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<...>>
definition .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<...>>.schema: SchedulerSchema<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 [...]>>
schema .SchedulerSchema<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 [...]>>.card: SRSSchema<{
input: Readonly<Omit<Readonly<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep?: number | undefined;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>;
output: Mutable<...>;
}>
card .SRSSchema<{ input: Readonly<Omit<Readonly<{ stability: number; difficulty: number; } & { dueAt: Date; lastReviewAt: Date | null; } & SchedulerCoreFields$1<"new" | "learning" | "review">> & { ...; }, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>; output: Mutable<...>; }>.parse(input: unknown): Mutable<Omit<Mutable<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep: number;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>
parse ({
cardId,
dueAt: new Date(stored.due),
lastReviewAt:
stored.state === const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .New || stored.last_review == null
? null
: new Date(stored.last_review),
stability: stored.stability,
difficulty: stored.difficulty,
state: stored.state,
scheduleStatus: scheduleStatus[stored.state],
scheduledDays: stored.scheduled_days,
learningStep: stored.learning_steps,
reps: stored.reps,
lapses: stored.lapses,
})
}
export const storedCard = {
due: '2026-01-03T00:00:00.000Z',
last_review: '2026-01-01T00:00:00.000Z',
stability: 2,
difficulty: 5,
state: const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .Review,
scheduled_days: 2,
learning_steps: 0,
reps: 3,
lapses: 0,
} satisfies type StoredV5Card = {
due: string;
last_review?: string | null;
stability: number;
difficulty: number;
state: State;
scheduled_days: number;
learning_steps: number;
reps: number;
lapses: number;
}
StoredV5Card
const const card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card = function convertCard(stored: StoredV5Card, cardId: Card["cardId"]): Card convertCard (storedCard, 'card-1')
// An overdue review: previous review, original due date and now are distinct.
const now = new Date('2026-01-04T00:00:00.000Z')
const const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
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">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep?: number | undefined;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>;
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 })
const const restored: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
restored = 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<...>>.rollback: (input: {
readonly card: Mutable<Omit<Mutable<Omit<Mutable<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep: number;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>;
readonly revlog: Mutable<...>;
}) => {
...;
}
rollback ({
card: Mutable<Omit<Mutable<Omit<Mutable<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep: number;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>
card : const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card ,
revlog: Mutable<Omit<Mutable<Omit<Mutable<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
} & SchedulerCoreFields$1<"new" | "learning" | "review"> & {
readonly rating: Grade;
}> & {
readonly cardId: string | number;
readonly learningStep: number;
readonly scheduledDays: number;
}, "rating" | keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review"> & {
readonly rating: Grade;
}>, "rating" | keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...> & {
readonly rating: Grade;
}>
revlog : const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog ,
})
console.log(
JSON.stringify(
{
cardId: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card .cardId,
rating: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog .rating,
reps: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card .reps,
revlog: {
lastReviewAt: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog .lastReviewAt,
dueAt: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog .dueAt,
reviewTime: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog .reviewTime,
},
rollbackDueAt: const restored: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
restored .dueAt,
},
null,
2
)
)
局部类型 StoredV5Card 描述应用存储格式,并非从 legacy 库导入。将数据库现有稳定 ID 作为 cardId,应用自有字段单独保留。组合后的卡片 schema 会校验转换后的日期和字段;其他应用字段也应在存储读取边界校验。
#配置对照
| v5 配置 | v6 配置 | 迁移说明 |
|---|---|---|
w | weights | 此阶段保留用户的 FSRS-6 权重;示例为方便运行才使用默认值。 |
request_retention | desiredRetention | 需要目标保留率中间件。 |
maximum_interval | maximumInterval | 需要最大间隔中间件;单调间隔策略也使用此上限。 |
enable_fuzz | enableFuzz | 需要模糊中间件,保留旧设置。 |
enable_short_term | enableShortTerm | 对 FSRS-6,同时影响模型短期行为和显式学习步骤。 |
learning_steps / relearning_steps | learningSteps / relearningSteps | 保留配置的步骤数组和已有学习进度。 |
| 重新学习步骤数量 | numRelearningSteps | FSRS-6 需设为 relearningSteps.length。 |
forget(..., reset_count) | clearStatsOnForget | 改为实例配置;v5 默认 false,v6 统计中间件默认 true。 |
#卡片、日志与操作
| v5 存储字段 | v6 字段或处理方式 |
|---|---|
due | 卡片 dueAt,反序列化为 Date。 |
elapsed_days / 日志 last_elapsed_days | 已移除,使用所选 chrono 从时间戳计算经过天数;FSRS-7 重放不要使用这些旧的取整值。 |
last_review | lastReviewAt,缺失或 null 统一为 null。 |
scheduled_days | scheduledDays,由中间件提供。 |
learning_steps | learningStep,由中间件提供。 |
stability、difficulty、state、reps、lapses | API 迁移时保留;统计字段需要中间件。 |
| 应用记录 ID | cardId,卡片和日志使用同一 ID。 |
| 无对应字段 | scheduleStatus:New → new,Learning/Relearning → learning,Review → review。 |
复习日志 review | reviewTime,即实际复习时间,不是卡片到期时间。 |
结果 { card, log } | { card, revlog }。 |
像示例一样从调度器推导类型。卡片和日志字段取决于所选模型、chrono 和中间件。自定义 v5 策略需改写为相应的中间件 hook,不能仅改名或继续继承旧调度器。
| v5 操作(仅供对照) | v6 操作 |
|---|---|
createEmptyCard(now) | scheduler.newCard({ now, cardId }) |
f.next(card, now, grade) | scheduler.review({ card, now, grade }) |
f.repeat(card, now) | scheduler.preview({ card, now }) 返回可迭代对象,通过 Symbol.iterator 提供 iterator,可用 for...of 遍历。每个候选项包含 { grade, card, revlog },通过 candidate.grade 选择,不再按评分键索引对象。 |
f.rollback(card, log) | scheduler.rollback({ card, revlog }),使用返回的 v6 日志。 |
f.forget(card, now, reset_count) | scheduler.forget({ card, now });返回卡片,不是 { card, log },手动操作日志由应用记录。 |
f.get_retrievability(card, now, false) | 对有 lastReviewAt 的已学习卡片,调用 scheduler.model.forgettingCurve(card, scheduler.chrono.difference(card.lastReviewAt, now)),返回数值;新卡片单独处理,百分比由应用格式化。 |
review() 返回新数据,应同时持久化 result.card 和 result.revlog。新日志保存撤销所需的复习前字段,包括 dueAt、lastReviewAt。不能将旧日志的 due 简单改名就用于 v6 撤销:旧日志可能缺少必要的复习前快照。保留原始历史记录,仅转换评分和实际复习时间用于模型重放。跨版本撤销需要独立的应用快照或撤销边界。
即使模型和配置相同,也不保证所有学习步骤或模糊场景的间隔与 v5 完全一致。切换写入前应检查 New、Learning、Review、Relearning 的代表卡片。DefaultScheduler 是另一种预设入口,但默认模型为 FSRS-7;若在此阶段采用它,应明确指定 FSRS-6。
#为什么日志中的三个时间不能直接改名
dateChrono 的字段名是 reviewTime,不是 reviewAt。以逾期复习为例:卡片上次在 1 月 1 日复习,原定 1 月 3 日到期,实际在 1 月 4 日复习。
| 含义 | v5 普通复习日志 | v6 revlog |
|---|---|---|
| 上次复习时间:1 月 1 日 | due(旧实现写入 card.last_review || card.due) | lastReviewAt |
| 原定到期时间:1 月 3 日 | 对此已学习卡片没有单独记录 | dueAt |
| 本次实际复习时间:1 月 4 日 | review | reviewTime |
lastReviewAt 表示本次复习之前的上次复习时间,输入与输出的类型有所区别:
- 输入
DateRevlogInputFields中是可选的lastReviewAt?: Date | null。省略或传入undefined时,schema 会将其规范化为null;dueAt和reviewTime仍必须是有效的Date。 - 输出
DateRevlogFields中始终存在lastReviewAt: Date | null。正常的已学习卡片日志记录复习前的card.lastReviewAt;没有上次复习的新卡片,首次复习日志中的值为null。 null不会自动推算出缺失的上次复习时间。旧日志缺少该信息时,能通过 schema 校验不等于能精确撤销;不要用dueAt或reviewTime代替它。
复习返回的卡片中,lastReviewAt 已变为 1 月 4 日,dueAt 是新计算的到期时间;日志保存的是复习前的时间。使用这张卡片和 v6 日志撤销后,dueAt 恢复为 1 月 3 日,lastReviewAt 恢复为 1 月 1 日,提前或逾期复习也一样。这正是 chrono 重构时分离「计算经过天数的时间」与「供撤销恢复的原到期时间」的原因。上面的可运行示例会打印这三个字段。
不能统一执行 oldLog.due → revlog.dueAt:普通 v5 已学习卡片日志的 due 通常是上次复习时间;无 last_review 的首次复习日志才存原到期时间,Manual/遗忘日志又有自己的语义。v5 重置后的 New 卡片还可能携带旧 last_review,因此卡片转换示例对 State.New 统一输出 lastReviewAt: null。
只重建模型记忆时,仅映射 rating 和 review → reviewTime 即可。若要跨版本精确撤销,需要原始复习前卡片快照(包括两个时间字段及中间件字段),否则应设立撤销边界。不能用 review 或重新计算的间隔补造过去未记录的到期时间,尤其是在提前复习、学习步骤、模糊或手动调整之后。
#2. 从 FSRS-6 迁移到 FSRS-7
#权重和状态属于各自的模型
FSRS-6 使用 21 个权重,FSRS-7 需要独立的 34 个权重。使用 FSRS7_DEFAULT_WEIGHTS,或重新训练 FSRS-7 权重。不要给 FSRS-6 参数补值,也不要复用其训练权重。migrateFSRS7Parameters 仅为空输入提供默认值,不会转换旧模型的参数布局。
FSRS-7 记忆状态包含 stability、stabilityFast 和 difficulty,三个字段都应通过历史重建。重建结果只用于更新当前 card 的记忆状态(memoryState);历史 revlog 仅作为只读输入,不得修改、覆盖或补写其中的任何数据,包括过去记录的记忆状态。
不得为已有卡片直接设置 stabilityFast = 0.8 * stability,也不能保留 FSRS-6 的稳定性、难度后只追加快分量。模型初始化使用的系数不是已演化记忆状态的迁移公式。
设置 fractionalDays: true,保留两次复习相隔十分钟等小数天数。FSRS-7 的快轨迹是模型固有部分:安装学习步骤中间件后,enableShortTerm: false 仅关闭显式学习步骤,不会关闭快轨迹。FSRS-7 模型没有与 FSRS-6 的 numRelearningSteps 对应的配置项。
#FSRS-7 的默认步骤与学习状态
DefaultScheduler 对 FSRS-7 与 FSRS-3~6 一样默认使用学习步骤 ['1m', '10m'] 与重新学习步骤 ['10m'],并开启 allowModelOverride,让更长的模型间隔覆盖当前步骤。显式传入的步骤不会被默认值覆盖。enableShortTerm 仍默认 true,控制显式步骤,不控制 FSRS-7 快轨迹或按间隔划分的学习状态。
注册 schedulerLearningStepsMiddleware 后,状态由最终调度间隔决定,即使步骤关闭、为空或已完成:
| 复习前状态 | 最终间隔小于 graduatingInterval(默认一天) | 最终间隔至少 graduatingInterval |
|---|---|---|
| New / Learning | Learning,scheduleStatus: learning | Review,scheduleStatus: review |
| Review / Relearning | Relearning,scheduleStatus: learning | Review,scheduleStatus: review |
配置了步骤时,没有适用步骤的 Hard、Good、Easy 至少在 graduatingInterval 之后到期,因此走完步骤,以及 Review 卡片按 Hard、Good 或 Easy,都会进入 Review。开启 allowModelOverride 时,使用模型间隔的 Again 总是进入 Learning/Relearning,即使其间隔被限制为 graduatingInterval。
没有配置步骤时,不能假定「没有 Learning」「只有 Again 才进入 Relearning」或「Easy 一定进入 Review」。一般来说,Easy 跳过显式步骤,Good 仅在存在适用的下一步骤时推进,时长为 0 的步骤视为没有步骤。learningStep 只在卡片留在 Learning/Relearning 时保留下一步骤,卡片进入 Review 时重置为 0;它是步骤索引,不是复习次数。
这些转换属于中间件,不属于 FSRS7Model。只组合模型和 chrono 的裸调度器仍返回 Review,即使间隔小于一天。迁移应用队列、状态筛选和统计时,需要同步调整依赖旧行为的判断。
“重放记忆,保留调度安排”一节的完整迁移示例显式保留已有学习步骤,没有依赖默认值。若产品决定完全采用模型驱动的步骤,可明确配置 learningSteps: []、relearningSteps: [];这不等于移除学习步骤中间件。改变步骤策略应单独验证,不能当作卡片字段迁移顺便执行。
#迁移回忆概率调用
使用 scheduler.model.forgettingCurve(state, elapsedDays) 获取数值回忆概率,或 scheduler.model.algorithm.curve(elapsedDays, state) 检查快慢分量。两者均需完整 FSRS-7 状态:stability、stabilityFast、difficulty,不能只传 FSRS-6 的单个稳定性值。
也可以从 ts-fsrs/models/fsrs-7 导入独立函数 curve,调用 curve(weights, elapsedDays, state),无需创建算法实例;其中 weights 必须是有效的 34 个 FSRS-7 权重。该函数不执行权重校验:对存储或用户输入先做 schema/参数校验,不要用它探测权重是否有效。使用已迁移的记忆状态与同一份 FSRS-7 权重,elapsedDays 保留小数天数。
曲线结果包括 retrievability、derivative、fast、slow、fastWeight、slowWeight 和 total;total = fastWeight + slowWeight 是混合系数之和,不是总回忆概率。替代旧回忆概率调用时读取 retrievability,百分比由应用格式化。算法实例的曲线结果也包含 total。这些曲线接口只计算概率与分量,不迁移卡片、生成日志或安排到期时间。
#重放记忆,保留调度安排
下面是独立可运行示例。它接收已转换为 v6 结构的 FSRS-6 卡片,将存储日志的 review 转成 reviewTime,调用 Reschedule.replay(),不传入旧模型的 initialState。迁移时只重建记忆状态,不重新安排已有卡片的下次复习时间(dueAt)。之后用户复习该卡片时,再由 FSRS-7 计算新的下次复习时间。ID、上次复习时间、学习进度与统计也保持不变。
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 , type State = 0 | 1 | 2 | 3
const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State } 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 schedulerFuzzingMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>
schedulerFuzzingMiddleware ,
const schedulerLearningStepsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval?: number;
readonly allowModelOverride?: boolean;
};
output: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval: number;
readonly allowModelOverride: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
}>
schedulerLearningStepsMiddleware ,
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 ,
const schedulerMonotonicIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after fuzzing and learning steps, when present.
schedulerMonotonicIntervalMiddleware ,
const schedulerScheduledDaysMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly fractionalDays?: boolean | undefined;
};
output: {
readonly fractionalDays: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
}>
Register before interval middleware so it records their final value on unwind.
schedulerScheduledDaysMiddleware ,
const schedulerStatsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly clearStatsOnForget?: boolean | undefined;
};
output: {
readonly clearStatsOnForget: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly reps: number;
readonly lapses: number;
};
output: {
readonly reps: number;
readonly lapses: number;
};
}>;
}>
schedulerStatsMiddleware ,
} from 'ts-fsrs/middlewares'
import { const FSRS7_DEFAULT_WEIGHTS: number[] FSRS7_DEFAULT_WEIGHTS , const FSRS7Model: Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>
FSRS7Model } from 'ts-fsrs/models/fsrs-7'
import { class Reschedule<Scheduler extends AnySchedulerCore>Replays a review history on top of an existing srs-kit scheduler core.
The scheduler instance keeps the model and chronology coupled, allowing the
review-time, card and memory-state types to be inferred from the selected
core.
Reschedule } from 'ts-fsrs/reschedule'
export const const definition: CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition = defineScheduler<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>>(definition: {
...;
}): CustomScheduler<...>
defineScheduler ({
model: Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>
model : const FSRS7Model: Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>
FSRS7Model ,
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-7", SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, 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 enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>, ... 4 more ..., Middleware<...>]>(middlewares_0: Middleware<...>, middlewares_1: Middleware<...>, middlewares_2: Middleware<...>, middlewares_3: Middleware<...>, middlewares_4: Middleware<...>, middlewares_5: Middleware<...>, middlewares_6: 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 schedulerFuzzingMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>
schedulerFuzzingMiddleware ,
const schedulerStatsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly clearStatsOnForget?: boolean | undefined;
};
output: {
readonly clearStatsOnForget: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly reps: number;
readonly lapses: number;
};
output: {
readonly reps: number;
readonly lapses: number;
};
}>;
}>
schedulerStatsMiddleware ,
const schedulerScheduledDaysMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly fractionalDays?: boolean | undefined;
};
output: {
readonly fractionalDays: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
}>
Register before interval middleware so it records their final value on unwind.
schedulerScheduledDaysMiddleware ,
const schedulerLearningStepsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval?: number;
readonly allowModelOverride?: boolean;
};
output: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval: number;
readonly allowModelOverride: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
}>
schedulerLearningStepsMiddleware ,
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 ,
const schedulerMonotonicIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after fuzzing and learning steps, when present.
schedulerMonotonicIntervalMiddleware
)
export const const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler = const definition: CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition .CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>.create: (ctx: {
readonly config: Omit<SchedulerConfigInput<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, "fractionalDays" | ... 8 more ... | "clearStatsOnForget"> & {
...;
};
}) => SchedulerCore<...>
create ({
config: Omit<SchedulerConfigInput<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, "fractionalDays" | ... 8 more ... | "clearStatsOnForget"> & {
...;
}
config : {
weights: const FSRS7_DEFAULT_WEIGHTS: number[] FSRS7_DEFAULT_WEIGHTS ,
fractionalDays: true,
enableShortTerm: true, // Controls learning steps, not the model's fast trace.
desiredRetention: 0.9,
// Preserve existing steps explicitly instead of inheriting preset defaults.
learningSteps: ['1m', '10m'],
relearningSteps: ['10m'],
enableFuzz: false,
maximumInterval: 36500,
clearStatsOnForget: false,
},
})
type type Card = {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card = ReturnType<typeof const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler .SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>.newCard: (options?: (Omit<{
readonly now?: Date | undefined;
} & {
readonly cardId?: string | number | undefined;
}, "now"> & {
readonly now?: Date | undefined;
}) | undefined) => {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
newCard >
type type PreviousCard = {
state: State;
scheduleStatus: "new" | "learning" | "review";
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
}
PreviousCard = Omit<type Card = {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card , 'stabilityFast'>
type type StoredReview = {
rating: Rating;
review: string;
}
StoredReview = { rating: type Rating = 0 | 1 | 2 | 3 | 4 Rating ; review: string }
// completeHistory means all reviews since creation or the latest memory reset.
// Decide this from application records, not from the number of rows alone.
export function function migrateMemory(card: PreviousCard, logs: readonly StoredReview[], completeHistory: boolean): Card migrateMemory (
card: type PreviousCard = {
state: State;
scheduleStatus: "new" | "learning" | "review";
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
}
PreviousCard ,
logs: readonly type StoredReview = {
rating: Rating;
review: string;
}
StoredReview [],
completeHistory: boolean
): type Card = {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card {
if (card.state === const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .New) {
const { stability, stabilityFast, difficulty } = const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler .SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>.newCard: (options?: (Omit<{
readonly now?: Date | undefined;
} & {
readonly cardId?: string | number | undefined;
}, "now"> & {
readonly now?: Date | undefined;
}) | undefined) => {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
newCard ({
cardId: card.cardId,
})
return const definition: CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition .CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>.schema: SchedulerSchema<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>
schema .SchedulerSchema<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>.card: SRSSchema<{
input: Readonly<Omit<Readonly<{
stabilityFast: number;
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep?: number | undefined;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>;
output: Mutable<...>;
}>
card .SRSSchema<{ input: Readonly<Omit<Readonly<{ stabilityFast: number; stability: number; difficulty: number; } & { dueAt: Date; lastReviewAt: Date | null; } & SchedulerCoreFields$1<"new" | "learning" | "review">> & { ...; }, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>; output: Mutable<...>; }>.parse(input: unknown): Mutable<Omit<Mutable<{
stabilityFast: number;
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep: number;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>
parse ({
...card,
stability,
stabilityFast,
difficulty,
})
}
if (!completeHistory)
throw new Error('Keep this card on FSRS-6: incomplete history')
const history = logs
.filter(({ rating }) => rating !== const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Manual)
.map(({ rating, review }) => ({
rating,
reviewTime: new Date(review),
}))
// replay validates dates/ratings and sorts the non-manual history.
// Empty or Manual-only histories throw instead of clearing memory.
const { memoryState } = new new Reschedule<SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>>(scheduler: SchedulerCore<...>): Reschedule<...>
Replays a review history on top of an existing srs-kit scheduler core.
The scheduler instance keeps the model and chronology coupled, allowing the
review-time, card and memory-state types to be inferred from the selected
core.
Reschedule (const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler ).Reschedule<SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>>.replay(input: ReplayInput<{
stabilityFast: number;
stability: number;
difficulty: number;
}, Date>, options?: RescheduleOptions): ReplayResult<{
stabilityFast: number;
stability: number;
difficulty: number;
}>
Rebuilds model memory from the review history and returns the memory state
after each review.
replay ({ ReplayInput<{ stabilityFast: number; stability: number; difficulty: number; }, Date>.history: readonly RescheduleReview<Date>[] history })
const latestReview = history.reduce(
(latest, { reviewTime }) => Math.max(latest, reviewTime.getTime()),
-Infinity
)
if (card.lastReviewAt?.getTime() !== latestReview) {
throw new Error(
'Keep this card on FSRS-6: history does not match lastReviewAt'
)
}
// Only the model memory changes; dueAt, counters and learning progress survive.
return const definition: CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition .CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>.schema: SchedulerSchema<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>
schema .SchedulerSchema<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>.card: SRSSchema<{
input: Readonly<Omit<Readonly<{
stabilityFast: number;
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep?: number | undefined;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>;
output: Mutable<...>;
}>
card .SRSSchema<{ input: Readonly<Omit<Readonly<{ stabilityFast: number; stability: number; difficulty: number; } & { dueAt: Date; lastReviewAt: Date | null; } & SchedulerCoreFields$1<"new" | "learning" | "review">> & { ...; }, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>; output: Mutable<...>; }>.parse(input: unknown): Mutable<Omit<Mutable<{
stabilityFast: number;
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep: number;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>
parse ({ ...card, ...memoryState })
}
export const previousCard: type PreviousCard = {
state: State;
scheduleStatus: "new" | "learning" | "review";
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
}
PreviousCard = {
cardId: 'card-1',
dueAt: new Date('2026-01-05T00:00:00.000Z'),
lastReviewAt: new Date('2026-01-02T00:00:00.000Z'),
stability: 2,
difficulty: 5,
state: const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .Review,
scheduleStatus: 'review',
scheduledDays: 3,
learningStep: 0,
reps: 3,
lapses: 0,
}
export const storedHistory: type StoredReview = {
rating: Rating;
review: string;
}
StoredReview [] = [
{ rating: const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, review: '2026-01-01T00:00:00.000Z' },
{ rating: const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, review: '2026-01-01T00:10:00.000Z' },
{ rating: const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, review: '2026-01-02T00:00:00.000Z' },
]
const const migrated: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
migrated = function migrateMemory(card: PreviousCard, logs: readonly StoredReview[], completeHistory: boolean): Card migrateMemory (previousCard, storedHistory, true)
console.log(
JSON.stringify(
{
model: 'FSRS-7',
cardId: const migrated: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
migrated .cardId,
dueAt: const migrated: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
migrated .dueAt,
reps: const migrated: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
migrated .reps,
stabilityFast: const migrated: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
migrated .stabilityFast,
},
null,
2
)
)
| 卡片或历史情况 | 处理方式 |
|---|---|
新卡片(State.New),含主动重置后的卡片 | 使用 FSRS-7 默认记忆状态,保留调度安排与统计。 |
| 已学习卡片,具有从创建或最近一次记忆重置以来的完整历史 | 使用 FSRS-7 权重重放,保存重建状态并保留调度安排。 |
| 历史缺失或被截断 | 卡片及其匹配配置继续使用 FSRS-6;不得静默清零,也不能仅凭行数宣称历史完整。 |
| 已学习卡片的历史为空或仅有 Manual | 重放抛错,卡片继续使用 FSRS-6。 |
日期或评分无效,或最后一次复习与 lastReviewAt 不匹配 | 停止该卡片迁移,先核对记录。 |
replay() 按时间排序有效复习并过滤 Rating.Manual,但不会解释手动遗忘或重置事件。应用必须选取最近一次记忆重置之后的完整历史,排除更早记录;仅手动调整到期时间不等于重置记忆。同一时间戳的复习应保留记录顺序。使用 dateChrono 时,时间需反序列化为有效 Date。首条保留的复习从未初始化的记忆状态开始,经过天数为零。
示例的历史完整标志由应用判断,最后时间戳匹配并不能证明更早的复习没有缺失。迁移失败的卡片应按持久化的模型版本继续路由到 FSRS-6,不要将它们传给 FSRS-7。
#如果确实需要重新安排到期时间
new Reschedule(scheduler).reschedule({ history, initialCard }) 会通过整套调度策略重放,返回 { card, results },可能改变到期时间、学习进度和统计。应以保留原 cardId、使用首条复习时间的 FSRS-7 新卡片作为 initialCard,不能在已有已学习卡片上再重放完整历史。生成的日志是重新计算的结果,不应替换原始历史。本文默认使用 replay(),迁移时不重新安排已有卡片的下次复习时间。
#持久化与验收
- 备份卡片、原始日志和配置。为每张卡片或与之可靠关联的配置保存模型版本,以及重放实际使用的权重。
- 增加
stabilityFast和 v6 字段的存储,将重建记忆、模型版本及对应权重/配置引用一并提交,避免混用 FSRS-6 状态和 FSRS-7 权重。保留应用 ID 与元数据。 - 保持原始日志不变,并建立撤销边界:旧模型日志不得直接用于 FSRS-7
rollback()。新的 FSRS-7 日志应保存stabilityFast和复习前字段。 - 使用代表卡片验证复习、预览、撤销、同日复习、学习步骤及配置的模糊行为。仅迁移记忆时,断言到期时间、ID、学习进度和统计不变。处理失败后再切换相应卡片的模型。