#ミドルウェア概要
ミドルウェアは、スケジューラにスケジューリングのポリシーやアプリケーション固有のフィールドを追加します。モデルが記憶状態と間隔を計算し、chrono のプリセットがその間隔を期限に変換します。ミドルウェアはその間に位置し、目標保持率や間隔の調整、学習ステップの管理、モデルが扱わないカードの追加データの更新を担います。各部分の役割はアーキテクチャを参照してください。
スケジューラはモデル・chrono・ミドルウェアを組み合わせます。モデルは記憶状態の更新と間隔計算に限定し、chrono は時間値と日数演算を担います。ミドルウェアはコア操作の前に入力を提供し、その後に結果を調整し、両者が定義しないフィールドを保持します。
このプロンプトをコピーすると、AI Agent がスケジューラの内部や永続化に踏み込まずに Middleware 契約へ従います。
#フィールドと処理を定義する
Middleware の定義には必須の name と、任意のスキーマ、デフォルト値、ハンドラがあります。defineMiddleware() はスキーマからハンドラ内の型を推論します。ts-fsrs からインポートでき、組み込みポリシーは ts-fsrs/middlewares からも利用できます。
| 定義 | 用途 |
|---|---|
schema.config | create({ config }) に渡す設定を検証します。ミドルウェアはスケジューラの設定オブジェクトを共有します。 |
schema.card | 入出力カードのフィールドを宣言し、検証します。 |
schema.revlog | ロールバックに必要な値を含め、復習ログのフィールドを宣言し、検証します。 |
schema.cardInitInput | カード ID など、newCard() が受け取る追加の入力を宣言します。 |
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 を更新します。実行方法はライフサイクルを参照してください。 |
スキーマは実行時の検証と 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 で組み合わせるため、ハンドラはスケジューラのコアを囲んで一定の順序で実行されます——詳細はライフサイクルを参照してください。
既成のポリシーは一般的なケースをカバーし、DefaultScheduler は既にそれらを組み合わせています。組み込みポリシー を参照するか、既成のスケジューラを使う場合はクイックスタートを参照してください。