#中间件概览
中间件为调度器添加调度策略和应用所需的字段。模型计算记忆状态与间隔,chrono 预设把这些间隔转换为到期时间;中间件位于两者之间,可以调整目标保留率或间隔、管理学习步骤,以及维护模型从未见过的额外卡片数据。各部分的职责见整体架构。
调度器由模型、chrono 与中间件组合而成。让模型只负责记忆状态更新与间隔计算,让 chrono 负责时间值与天数运算;中间件在核心操作之前提供输入、在其后调整结果,并承载这两者未定义的字段。
复制这段提示词,让 AI Agent 遵守 Middleware 契约,避免混入调度器内部实现或持久化逻辑。
#定义字段与行为
Middleware 定义包含必填的 name,以及可选的 schema、默认值和处理函数。defineMiddleware() 根据 schema 推导处理函数中的类型。可以从 ts-fsrs 导入;内置策略也可从 ts-fsrs/middlewares 导入。
| 定义 | 用途 |
|---|---|
schema.config | 校验传给 create({ config }) 的配置。中间件共用调度器的配置对象。 |
schema.card | 声明并校验输入、输出卡片上的字段。 |
schema.revlog | 声明并校验复习日志字段,包括撤销所需的旧值。 |
schema.cardInitInput | 声明 newCard() 接受的额外输入,例如卡片 ID。 |
scheduleStatus | 注册自定义调度状态,例如 scheduleStatus: ['buried'],使组合后的类型和结果校验接受这些状态。 |
handlers.nextInterval | 在 next() 前调整 ctx.candidate.desiredRetention[ctx.input.grade],或在其后调整 ctx.scheduledDays。复习和直接调用 scheduler.nextInterval() 时都会执行。 |
defaultValue.card | 在创建或重置卡片时提供字段值,通过 ctx.operation 区分这两种操作。 |
handlers.review / handlers.rollback | 在调用下一个处理函数前后,更新 ctx.result。运行方式见生命周期。 |
Schema 同时提供运行时校验与 TypeScript 类型推导。下面使用实现了 Standard Schema 的 Zod。在自己的应用中运行示例时,除 ts-fsrs 外还需要安装它:
npm install zodyarn add zodpnpm add zodbun add zoddeno add npm:zod#添加支持撤销的连续答对计数
示例中,除 Again 外的评分都会让 streak 加一,最多累积到 streakLimit;Again 会将它清零。复习处理函数把原计数保存到 revlog,因此即使清零后撤销,也能恢复之前的值:
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 defineMiddleware<const Name extends PropertyKey, const Schema extends MiddlewareDefinitionSchema = EmptyPart, const Status extends string = never>(definition: MiddlewareDefinition<Schema, Name, Status>): Middleware<Name, Prettify<Assign<MiddlewareStatusEnv<Status>, Schema>> extends MiddlewareEnv ? MiddlewareEnv & Prettify<Assign<MiddlewareStatusEnv<Status>, Schema>> : never> defineMiddleware , 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 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'
import { z } from 'zod'
const streakFields = z.object({ streak: z.int().nonnegative() })
const const streakMiddleware: Middleware<symbol, {
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}>
streakMiddleware = defineMiddleware<symbol, {
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}, never>(definition: MiddlewareDefinition<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}, symbol, never>): Middleware<...>
defineMiddleware ({
name: Symbol('guide.streak'),
schema: {
config: z.object({ streakLimit: z.int().positive() }),
card: streakFields,
revlog: streakFields,
},
defaultValue?: {
readonly card?: FieldDefault<z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>, MiddlewareDefaultValueContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}>> | undefined;
readonly revlog?: FieldDefault<z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>, MiddlewareDefaultValueContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}>> | undefined;
} | undefined
defaultValue : { card?: FieldDefault<z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>, MiddlewareDefaultValueContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}>> | undefined
card : () => ({ streak: 0 }) },
handlers?: {
readonly nextInterval?: NextIntervalMiddlewareHandler<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> | undefined;
readonly review?: MiddlewareHandler<ReviewMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<...>> | undefined;
readonly rollback?: MiddlewareHandler<...> | undefined;
} | undefined
handlers : {
review?: MiddlewareHandler<ReviewMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<never, "card", "card" | "revlog">> | undefined
review (ctx: ReviewMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<never, "card", "card" | "revlog">
ctx , next) {
next()
const { card, grade } = ctx: ReviewMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<never, "card", "card" | "revlog">
ctx .input: {
readonly card: {
streak: number;
} & SchedulerCoreFields<string>;
readonly grade: Grade;
readonly desiredRetention?: number;
} & {
readonly now: unknown;
} & {
readonly card: {
readonly scheduleStatus: "new" | "learning" | "review";
};
}
input
ctx: ReviewMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<never, "card", "card" | "revlog">
ctx .result: ReviewMiddlewareResult<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & {
readonly card: {
scheduleStatus?: "new" | "learning" | "review" | undefined;
};
readonly revlog: {
scheduleStatus?: "new" | "learning" | "review" | undefined;
};
}
result .revlog: Partial<Mutable<{
streak: number;
} & SchedulerCoreFields<string> & {
readonly rating: Grade;
}>> & Record<string, unknown> & {
scheduleStatus?: "new" | "learning" | "review" | undefined;
}
revlog .streak = card.streak
ctx: ReviewMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<never, "card", "card" | "revlog">
ctx .result: ReviewMiddlewareResult<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & {
readonly card: {
scheduleStatus?: "new" | "learning" | "review" | undefined;
};
readonly revlog: {
scheduleStatus?: "new" | "learning" | "review" | undefined;
};
}
result .card: Partial<Mutable<{
streak: number;
} & SchedulerCoreFields<string>>> & Record<string, unknown> & {
scheduleStatus?: "new" | "learning" | "review" | undefined;
}
card .streak =
grade === const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Again
? 0
: Math.min(card.streak + 1, ctx: ReviewMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<never, "card", "card" | "revlog">
ctx .MiddlewareContextBase<{ readonly config: ZodObject<{ streakLimit: ZodInt; }, $strip>; readonly card: ZodObject<{ streak: ZodInt; }, $strip>; readonly revlog: ZodObject<...>; }>.config: MiddlewareContextConfig<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}>
config .streakLimit)
},
rollback?: MiddlewareHandler<RollbackMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<never, "card" | "revlog", "card">> | undefined
rollback (ctx: RollbackMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<never, "card" | "revlog", "card">
ctx , next) {
next()
ctx: RollbackMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<never, "card" | "revlog", "card">
ctx .result: RollbackMiddlewareResult<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & {
readonly card: {
scheduleStatus?: "new" | "learning" | "review" | undefined;
};
}
result .card: Partial<Mutable<{
streak: number;
} & SchedulerCoreFields<string>>> & Record<string, unknown> & {
scheduleStatus?: "new" | "learning" | "review" | undefined;
}
card .streak = ctx: RollbackMiddlewareContext<{
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}> & MiddlewareStatusContext<never, "card" | "revlog", "card">
ctx .input: {
readonly card: {
streak: number;
} & SchedulerCoreFields<string>;
readonly revlog: {
streak: number;
} & SchedulerCoreFields<string> & {
readonly rating: Grade;
};
} & {
readonly card: {
readonly scheduleStatus: "new" | "learning" | "review";
};
readonly revlog: {
readonly scheduleStatus: "new" | "learning" | "review";
};
}
input .revlog: {
streak: number;
} & SchedulerCoreFields<string> & {
readonly rating: Grade;
} & {
readonly scheduleStatus: "new" | "learning" | "review";
}
revlog .streak
},
},
})
const const base: 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<...>>
base = 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 })
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 base: 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<...>>
base .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: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}>]>(middlewares_0: Middleware<symbol, {
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}>) => CustomScheduler<...>
Returns a new scheduler definition extended with the supplied middleware.
The current scheduler and any cores created from it remain unchanged.
use (const streakMiddleware: Middleware<symbol, {
readonly config: z.ZodObject<{
streakLimit: z.ZodInt;
}, z.core.$strip>;
readonly card: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
readonly revlog: z.ZodObject<{
streak: z.ZodInt;
}, z.core.$strip>;
}>
streakMiddleware ).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 & {
streakLimit: number;
};
}) => 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 & {
streakLimit: number;
}
config : {
weights: const FSRS6_DEFAULT_WEIGHTS: number[] FSRS6_DEFAULT_WEIGHTS ,
enableShortTerm: false,
numRelearningSteps: 0,
streakLimit: 2,
},
})
const now = new Date('2026-01-01T00:00:00.000Z')
const const initial: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
initial = 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) => {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
newCard ({ now })
const const good: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
good = 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">> & {
streak: number;
}, 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: const initial: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
initial , grade: const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, now })
const const again: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
again = 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">> & {
streak: number;
}, 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: const good: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
good .card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card ,
grade: const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Again,
now: const good: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
good .card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card .dueAt,
})
const const restored: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
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">> & {
streak: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>;
readonly revlog: Mutable<...>;
}) => {
...;
}
rollback (const again: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
again )
const const preview: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
readonly grade: Grade;
}[]
preview = Array.ArrayConstructor.from<{
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
readonly grade: Grade;
}>(iterable: Iterable<{
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
readonly grade: Grade;
}> | ArrayLike<...>): {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
readonly grade: Grade;
}[] (+3 overloads)
Creates an array from an iterable object.
@paramiterable An iterable object to convert to an array. from (
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<...>>.preview: (input: {
readonly card: Readonly<Omit<Readonly<Omit<Readonly<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
streak: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>;
readonly now?: Date | undefined;
}) => PreviewResult<...>
preview ({ card: const restored: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
restored , now: const good: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
good .card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card .dueAt })
)
console.log(
JSON.stringify(
{
initial: const initial: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
initial .streak,
afterGood: const good: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
good .card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card .streak,
afterAgain: const again: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
again .card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card .streak,
savedBeforeAgain: const again: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
again .revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog .streak,
restored: const restored: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
restored .streak,
preview: const preview: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
readonly grade: Grade;
}[]
preview .Array<{ card: { streak: number; stability: number; difficulty: number; dueAt: Date; lastReviewAt: Date | null; state: State; scheduleStatus: "new" | "learning" | "review"; }; revlog: { ...; }; readonly grade: Grade; }>.map<number>(callbackfn: (value: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
readonly grade: Grade;
}, index: number, array: {
card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
readonly grade: Grade;
}[]) => number, thisArg?: any): number[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. map (({ card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card }) => card: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card .streak),
afterPreview: const restored: {
streak: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
restored .streak,
},
null,
2
)
)
计数从 0 增加到 1,评为 Again 后回到 0,撤销后恢复为 1。预览中,Again、Hard、Good、Easy 对应的计数分别是 0、2、2、2,输入卡片的计数仍为 1。
base.use(streakMiddleware) 返回新的调度器定义,原来的 base 不变。需要保存返回值,或像示例一样继续调用 .create()。由于中间件通过 use 组合,处理函数会按固定顺序环绕调度器核心——详见生命周期。