ts-fsrs 4.7.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -23
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +136 -74
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +26 -21
package/README.md
CHANGED
|
@@ -2,29 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
# About
|
|
5
|
+
# About
|
|
6
6
|
[](https://www.npmjs.com/package/ts-fsrs)
|
|
7
7
|
[](https://www.npmjs.com/package/ts-fsrs)
|
|
8
8
|
[](https://codecov.io/gh/open-spaced-repetition/ts-fsrs)
|
|
9
9
|
[](https://github.com/open-spaced-repetition/ts-fsrs/actions/workflows/npm-publish.yml)
|
|
10
10
|
[](https://github.com/open-spaced-repetition/ts-fsrs/actions/workflows/deploy.yml)
|
|
11
11
|
|
|
12
|
-
ts-fsrs is a versatile package
|
|
12
|
+
ts-fsrs is a versatile package written in TypeScript that supports [ES modules](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c), [CommonJS](https://en.wikipedia.org/wiki/CommonJS), and UMD. It implements the [Free Spaced Repetition Scheduler (FSRS) algorithm](https://github.com/open-spaced-repetition/free-spaced-repetition-scheduler), enabling developers to integrate FSRS into their flashcard applications to enhance the user learning experience.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
> - google
|
|
14
|
+
You can find the state transition diagram for cards here:
|
|
15
|
+
> - google drive: [ts-fsrs-workflow.drawio](https://drive.google.com/file/d/1FLKjpt4T3Iis02vjoA10q7vxKCWwClfR/view?usp=sharing) (You're free to leave comments)
|
|
16
16
|
> - github: [ts-fsrs-workflow.drawio](./ts-fsrs-workflow.drawio)
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
# Usage
|
|
20
|
-
|
|
20
|
+
`ts-fsrs@3.x` requires Node.js version `16.0.0` or higher. Starting with `ts-fsrs@4.x`, the minimum required Node.js version is `18.0.0`.
|
|
21
21
|
From version `3.5.6` onwards, ts-fsrs supports CommonJS, ESM, and UMD module systems.
|
|
22
22
|
|
|
23
23
|
```
|
|
24
|
-
npm install ts-fsrs
|
|
25
|
-
yarn
|
|
26
|
-
pnpm install ts-fsrs
|
|
27
|
-
bun
|
|
24
|
+
npm install ts-fsrs # npm install github:open-spaced-repetition/ts-fsrs
|
|
25
|
+
yarn add ts-fsrs
|
|
26
|
+
pnpm install ts-fsrs # pnpm install github:open-spaced-repetition/ts-fsrs
|
|
27
|
+
bun add ts-fsrs
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
# Example
|
|
@@ -38,7 +38,6 @@ const card = createEmptyCard(new Date('2022-2-1 10:00:00'));// createEmptyCard()
|
|
|
38
38
|
const now = new Date('2022-2-2 10:00:00');// new Date();
|
|
39
39
|
const scheduling_cards = f.repeat(card, now);
|
|
40
40
|
|
|
41
|
-
// console.log(scheduling_cards);
|
|
42
41
|
for (const item of scheduling_cards) {
|
|
43
42
|
// grades = [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy]
|
|
44
43
|
const grade = item.log.rating
|
|
@@ -62,17 +61,17 @@ for (const item of scheduling_cards) {
|
|
|
62
61
|
}
|
|
63
62
|
```
|
|
64
63
|
|
|
65
|
-
More
|
|
64
|
+
More resources:
|
|
66
65
|
- [Docs - Github Pages](https://open-spaced-repetition.github.io/ts-fsrs/)
|
|
67
66
|
- [Example.html - Github Pages](https://open-spaced-repetition.github.io/ts-fsrs/example)
|
|
68
|
-
- [Browser](https://github.com/open-spaced-repetition/ts-fsrs/blob/
|
|
69
|
-
- [ts-fsrs-demo - Next.js+
|
|
67
|
+
- [Browser](https://github.com/open-spaced-repetition/ts-fsrs/blob/main/example/example.html) (ts-fsrs package using CDN)
|
|
68
|
+
- [ts-fsrs-demo - Next.js+Hono.js+kysely](https://github.com/ishiko732/ts-fsrs-demo)
|
|
70
69
|
- [spaced - Next.js+Drizzle+tRPC](https://github.com/zsh-eng/spaced)
|
|
71
70
|
|
|
72
71
|
# Basic Use
|
|
73
72
|
|
|
74
73
|
## 1. **Initialization**:
|
|
75
|
-
To begin, create an empty card instance and set the current date(default: current time from system):
|
|
74
|
+
To begin, create an empty card instance and set the current date (default: current time from the system):
|
|
76
75
|
|
|
77
76
|
```typescript
|
|
78
77
|
import { Card, createEmptyCard } from "ts-fsrs";
|
|
@@ -83,7 +82,7 @@ let card: Card = createEmptyCard();
|
|
|
83
82
|
```
|
|
84
83
|
|
|
85
84
|
## 2. **Parameter Configuration**:
|
|
86
|
-
The library
|
|
85
|
+
The library has multiple modifiable "SRS parameters" (settings, besides the weight/parameter values). Use `generatorParameters` to set these parameters for the SRS algorithm. Here's an example for setting a maximum interval:
|
|
87
86
|
|
|
88
87
|
```typescript
|
|
89
88
|
import { Card, createEmptyCard, generatorParameters, FSRSParameters } from "ts-fsrs";
|
|
@@ -92,7 +91,7 @@ const params: FSRSParameters = generatorParameters({ maximum_interval: 1000 });
|
|
|
92
91
|
```
|
|
93
92
|
|
|
94
93
|
## 3. **Scheduling with FSRS**:
|
|
95
|
-
The core functionality lies in the `fsrs`
|
|
94
|
+
The core functionality lies in the `repeat` function of the `fsrs` class. When invoked, it returns a set of cards scheduled based on different potential user ratings:
|
|
96
95
|
|
|
97
96
|
```typescript
|
|
98
97
|
import {
|
|
@@ -139,11 +138,12 @@ Each `Card` object consists of various attributes that determine its status, sch
|
|
|
139
138
|
|
|
140
139
|
```typescript
|
|
141
140
|
type Card = {
|
|
142
|
-
due: Date;
|
|
143
|
-
stability: number;
|
|
144
|
-
difficulty: number;
|
|
145
|
-
elapsed_days: number;
|
|
146
|
-
scheduled_days: number
|
|
141
|
+
due: Date; // Date when the card is next due for review
|
|
142
|
+
stability: number; // A measure of how well the information is retained
|
|
143
|
+
difficulty: number; // Reflects the inherent difficulty of the card content
|
|
144
|
+
elapsed_days: number; // Days since the card was last reviewed
|
|
145
|
+
scheduled_days: number;// The interval of time in days between this review and the next one
|
|
146
|
+
learning_steps: number;// Keeps track of the current step during the (re)learning stages
|
|
147
147
|
reps: number; // Total number of times the card has been reviewed
|
|
148
148
|
lapses: number; // Times the card was forgotten or remembered incorrectly
|
|
149
149
|
state: State; // The current state of the card (New, Learning, Review, Relearning)
|
|
@@ -152,7 +152,7 @@ type Card = {
|
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
## 6. **Understanding Log Attributes**:
|
|
155
|
-
Each `ReviewLog` object contains various attributes that
|
|
155
|
+
Each `ReviewLog` object contains various attributes that represent a review that was done on a card. Used for analysis, undoing the review, and [optimization (WIP)](https://github.com/open-spaced-repetition/fsrs-rs-nodejs).
|
|
156
156
|
|
|
157
157
|
```typescript
|
|
158
158
|
type ReviewLog = {
|
|
@@ -164,6 +164,7 @@ type ReviewLog = {
|
|
|
164
164
|
elapsed_days: number; // Number of days elapsed since the last review
|
|
165
165
|
last_elapsed_days: number; // Number of days between the last two reviews
|
|
166
166
|
scheduled_days: number; // Number of days until the next review
|
|
167
|
+
learning_steps: number; // Keeps track of the current step during the (re)learning stages
|
|
167
168
|
review: Date; // Date of the review
|
|
168
169
|
}
|
|
169
|
-
```
|
|
170
|
+
```
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var c=(s=>(s[s.New=0]="New",s[s.Learning=1]="Learning",s[s.Review=2]="Review",s[s.Relearning=3]="Relearning",s))(c||{}),l=(s=>(s[s.Manual=0]="Manual",s[s.Again=1]="Again",s[s.Hard=2]="Hard",s[s.Good=3]="Good",s[s.Easy=4]="Easy",s))(l||{});class h{static card(t){return{...t,state:h.state(t.state),due:h.time(t.due),last_review:t.last_review?h.time(t.last_review):void 0}}static rating(t){if(typeof t=="string"){const e=t.charAt(0).toUpperCase(),i=t.slice(1).toLowerCase(),a=l[`${e}${i}`];if(a===void 0)throw new Error(`Invalid rating:[${t}]`);return a}else if(typeof t=="number")return t;throw new Error(`Invalid rating:[${t}]`)}static state(t){if(typeof t=="string"){const e=t.charAt(0).toUpperCase(),i=t.slice(1).toLowerCase(),a=c[`${e}${i}`];if(a===void 0)throw new Error(`Invalid state:[${t}]`);return a}else if(typeof t=="number")return t;throw new Error(`Invalid state:[${t}]`)}static time(t){if(typeof t=="object"&&t instanceof Date)return t;if(typeof t=="string"){const e=Date.parse(t);if(isNaN(e))throw new Error(`Invalid date:[${t}]`);return new Date(e)}else if(typeof t=="number")return new Date(t);throw new Error(`Invalid date:[${t}]`)}static review_log(t){return{...t,due:h.time(t.due),rating:h.rating(t.rating),state:h.state(t.state),review:h.time(t.review)}}}const B="4.7.0";Date.prototype.scheduler=function(s,t){return I(this,s,t)},Date.prototype.diff=function(s,t){return L(this,s,t)},Date.prototype.format=function(){return C(this)},Date.prototype.dueFormat=function(s,t,e){return G(this,s,t,e)};function I(s,t,e){return new Date(e?h.time(s).getTime()+t*24*60*60*1e3:h.time(s).getTime()+t*60*1e3)}function L(s,t,e){if(!s||!t)throw new Error("Invalid date");const i=h.time(s).getTime()-h.time(t).getTime();let a=0;switch(e){case"days":a=Math.floor(i/(24*60*60*1e3));break;case"minutes":a=Math.floor(i/(60*1e3));break}return a}function C(s){const t=h.time(s),e=t.getFullYear(),i=t.getMonth()+1,a=t.getDate(),r=t.getHours(),n=t.getMinutes(),d=t.getSeconds();return`${e}-${p(i)}-${p(a)} ${p(r)}:${p(n)}:${p(d)}`}function p(s){return s<10?`0${s}`:`${s}`}const S=[60,60,24,31,12],E=["second","min","hour","day","month","year"];function G(s,t,e,i=E){s=h.time(s),t=h.time(t),i.length!==E.length&&(i=E);let a=s.getTime()-t.getTime(),r;for(a/=1e3,r=0;r<S.length&&!(a<S[r]);r++)a/=S[r];return`${Math.floor(a)}${e?i[r]:""}`}function J(s){return h.time(s)}function K(s){return h.state(s)}function Q(s){return h.rating(s)}const N=[l.Again,l.Hard,l.Good,l.Easy],Z=[{start:2.5,end:7,factor:.15},{start:7,end:20,factor:.1},{start:20,end:1/0,factor:.05}];function k(s,t,e){let i=1;for(const n of Z)i+=n.factor*Math.max(Math.min(s,n.end)-n.start,0);s=Math.min(s,e);let a=Math.max(2,Math.round(s-i));const r=Math.min(Math.round(s+i),e);return s>t&&(a=Math.max(a,t+1)),a=Math.min(a,r),{min_ivl:a,max_ivl:r}}function m(s,t,e){return Math.min(Math.max(s,t),e)}function z(s,t){const e=Date.UTC(s.getUTCFullYear(),s.getUTCMonth(),s.getUTCDate()),i=Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate());return Math.floor((i-e)/864e5)}const T=.9,U=36500,q=[.40255,1.18385,3.173,15.69105,7.1949,.5345,1.4604,.0046,1.54575,.1192,1.01925,1.9395,.11,.29605,2.2698,.2315,2.9898,.51655,.6621],P=!1,Y=!0,tt=`v${B} using FSRS-5.0`,_=.01,v=100,R=[[_,v],[_,v],[_,v],[_,v],[1,10],[.001,4],[.001,4],[.001,.75],[0,4.5],[0,.8],[.001,3.5],[.001,5],[.001,.25],[.001,.9],[0,4],[0,1],[1,6],[0,2],[0,2]],A=s=>{let t=q;return s?.w&&(s.w.length===19?t=s?.w:s.w.length===17&&(t=s?.w.concat([0,0]),t[4]=+(t[5]*2+t[4]).toFixed(8),t[5]=+(Math.log(t[5]*3+1)/3).toFixed(8),t[6]=+(t[6]+.5).toFixed(8),console.debug("[FSRS V5]auto fill w to 19 length"))),t=t.map((e,i)=>m(e,R[i][0],R[i][1])),{request_retention:s?.request_retention||T,maximum_interval:s?.maximum_interval||U,w:t,enable_fuzz:s?.enable_fuzz??P,enable_short_term:s?.enable_short_term??Y}};function x(s,t){const e={due:s?h.time(s):new Date,stability:0,difficulty:0,elapsed_days:0,scheduled_days:0,reps:0,lapses:0,state:c.New,last_review:void 0};return t&&typeof t=="function"?t(e):e}class et{c;s0;s1;s2;constructor(t){const e=it();this.c=1,this.s0=e(" "),this.s1=e(" "),this.s2=e(" "),t==null&&(t=+new Date),this.s0-=e(t),this.s0<0&&(this.s0+=1),this.s1-=e(t),this.s1<0&&(this.s1+=1),this.s2-=e(t),this.s2<0&&(this.s2+=1)}next(){const t=2091639*this.s0+this.c*23283064365386963e-26;return this.s0=this.s1,this.s1=this.s2,this.s2=t-(this.c=t|0),this.s2}set state(t){this.c=t.c,this.s0=t.s0,this.s1=t.s1,this.s2=t.s2}get state(){return{c:this.c,s0:this.s0,s1:this.s1,s2:this.s2}}}function it(){let s=4022871197;return function(t){t=String(t);for(let e=0;e<t.length;e++){s+=t.charCodeAt(e);let i=.02519603282416938*s;s=i>>>0,i-=s,i*=s,s=i>>>0,i-=s,s+=i*4294967296}return(s>>>0)*23283064365386963e-26}}function at(s){const t=new et(s),e=()=>t.next();return e.int32=()=>t.next()*4294967296|0,e.double=()=>e()+(e()*2097152|0)*11102230246251565e-32,e.state=()=>t.state,e.importState=i=>(t.state=i,e),e}const D=-.5,F=19/81;function O(s,t){return+Math.pow(1+F*s/t,D).toFixed(8)}class V{param;intervalModifier;_seed;constructor(t){this.param=new Proxy(A(t),this.params_handler_proxy()),this.intervalModifier=this.calculate_interval_modifier(this.param.request_retention)}get interval_modifier(){return this.intervalModifier}set seed(t){this._seed=t}calculate_interval_modifier(t){if(t<=0||t>1)throw new Error("Requested retention rate should be in the range (0,1]");return+((Math.pow(t,1/D)-1)/F).toFixed(8)}get parameters(){return this.param}set parameters(t){this.update_parameters(t)}params_handler_proxy(){const t=this;return{set:function(e,i,a){return i==="request_retention"&&Number.isFinite(a)&&(t.intervalModifier=t.calculate_interval_modifier(Number(a))),Reflect.set(e,i,a),!0}}}update_parameters(t){const e=A(t);for(const i in e)if(i in this.param){const a=i;this.param[a]=e[a]}}init_stability(t){return Math.max(this.param.w[t-1],.1)}init_difficulty(t){return this.constrain_difficulty(this.param.w[4]-Math.exp((t-1)*this.param.w[5])+1)}apply_fuzz(t,e){if(!this.param.enable_fuzz||t<2.5)return Math.round(t);const i=at(this._seed)(),{min_ivl:a,max_ivl:r}=k(t,e,this.param.maximum_interval);return Math.floor(i*(r-a+1)+a)}next_interval(t,e){const i=Math.min(Math.max(1,Math.round(t*this.intervalModifier)),this.param.maximum_interval);return this.apply_fuzz(i,e)}linear_damping(t,e){return+(t*(10-e)/9).toFixed(8)}next_difficulty(t,e){const i=-this.param.w[6]*(e-3),a=t+this.linear_damping(i,t);return this.constrain_difficulty(this.mean_reversion(this.init_difficulty(l.Easy),a))}constrain_difficulty(t){return Math.min(Math.max(+t.toFixed(8),1),10)}mean_reversion(t,e){return+(this.param.w[7]*t+(1-this.param.w[7])*e).toFixed(8)}next_recall_stability(t,e,i,a){const r=l.Hard===a?this.param.w[15]:1,n=l.Easy===a?this.param.w[16]:1;return+m(e*(1+Math.exp(this.param.w[8])*(11-t)*Math.pow(e,-this.param.w[9])*(Math.exp((1-i)*this.param.w[10])-1)*r*n),_,36500).toFixed(8)}next_forget_stability(t,e,i){return+m(this.param.w[11]*Math.pow(t,-this.param.w[12])*(Math.pow(e+1,this.param.w[13])-1)*Math.exp((1-i)*this.param.w[14]),_,36500).toFixed(8)}next_short_term_stability(t,e){return+m(t*Math.exp(this.param.w[17]*(e-3+this.param.w[18])),_,36500).toFixed(8)}forgetting_curve=O;next_state(t,e,i){const{difficulty:a,stability:r}=t??{difficulty:0,stability:0};if(e<0)throw new Error(`Invalid delta_t "${e}"`);if(i<0||i>4)throw new Error(`Invalid grade "${i}"`);if(a===0&&r===0)return{difficulty:this.init_difficulty(i),stability:this.init_stability(i)};if(i===0)return{difficulty:a,stability:r};if(a<1||r<_)throw new Error(`Invalid memory state { difficulty: ${a}, stability: ${r} }`);const n=this.forgetting_curve(e,r),d=this.next_recall_stability(a,r,n,i),u=this.next_forget_stability(a,r,n),o=this.next_short_term_stability(r,i);let f=d;if(i===1){let[y,w]=[0,0];this.param.enable_short_term&&(y=this.param.w[17],w=this.param.w[18]);const g=r/Math.exp(y*w);f=m(+g.toFixed(8),_,u)}return e===0&&this.param.enable_short_term&&(f=o),{difficulty:this.next_difficulty(a,i),stability:f}}}function $(){const s=this.review_time.getTime(),t=this.current.reps,e=this.current.difficulty*this.current.stability;return`${s}_${t}_${e}`}function rt(s){return function(){const t=Reflect.get(this.current,s)??0,e=this.current.reps;return String(t+e||0)}}var b=(s=>(s.SCHEDULER="Scheduler",s.SEED="Seed",s))(b||{});class H{last;current;review_time;next=new Map;algorithm;initSeedStrategy;constructor(t,e,i,a={seed:$}){this.algorithm=i,this.initSeedStrategy=a.seed.bind(this),this.last=h.card(t),this.current=h.card(t),this.review_time=h.time(e),this.init()}init(){const{state:t,last_review:e}=this.current;let i=0;t!==c.New&&e&&(i=z(e,this.review_time)),this.current.last_review=this.review_time,this.current.elapsed_days=i,this.current.reps+=1,this.algorithm.seed=this.initSeedStrategy()}preview(){return{[l.Again]:this.review(l.Again),[l.Hard]:this.review(l.Hard),[l.Good]:this.review(l.Good),[l.Easy]:this.review(l.Easy),[Symbol.iterator]:this.previewIterator.bind(this)}}*previewIterator(){for(const t of N)yield this.review(t)}review(t){const{state:e}=this.last;let i;switch(e){case c.New:i=this.newState(t);break;case c.Learning:case c.Relearning:i=this.learningState(t);break;case c.Review:i=this.reviewState(t);break}if(i)return i;throw new Error("Invalid grade")}buildLog(t){const{last_review:e,due:i,elapsed_days:a}=this.last;return{rating:t,state:this.current.state,due:e||i,stability:this.current.stability,difficulty:this.current.difficulty,elapsed_days:this.current.elapsed_days,last_elapsed_days:a,scheduled_days:this.current.scheduled_days,review:this.review_time}}}class j extends H{newState(t){const e=this.next.get(t);if(e)return e;const i=h.card(this.current);switch(i.difficulty=this.algorithm.init_difficulty(t),i.stability=this.algorithm.init_stability(t),t){case l.Again:i.scheduled_days=0,i.due=this.review_time.scheduler(1),i.state=c.Learning;break;case l.Hard:i.scheduled_days=0,i.due=this.review_time.scheduler(5),i.state=c.Learning;break;case l.Good:i.scheduled_days=0,i.due=this.review_time.scheduler(10),i.state=c.Learning;break;case l.Easy:{const r=this.algorithm.next_interval(i.stability,this.current.elapsed_days);i.scheduled_days=r,i.due=this.review_time.scheduler(r,!0),i.state=c.Review;break}default:throw new Error("Invalid grade")}const a={card:i,log:this.buildLog(t)};return this.next.set(t,a),a}learningState(t){const e=this.next.get(t);if(e)return e;const{state:i,difficulty:a,stability:r}=this.last,n=h.card(this.current),d=this.current.elapsed_days;switch(n.difficulty=this.algorithm.next_difficulty(a,t),n.stability=this.algorithm.next_short_term_stability(r,t),t){case l.Again:{n.scheduled_days=0,n.due=this.review_time.scheduler(5,!1),n.state=i;break}case l.Hard:{n.scheduled_days=0,n.due=this.review_time.scheduler(10),n.state=i;break}case l.Good:{const o=this.algorithm.next_interval(n.stability,d);n.scheduled_days=o,n.due=this.review_time.scheduler(o,!0),n.state=c.Review;break}case l.Easy:{const o=this.algorithm.next_short_term_stability(r,l.Good),f=this.algorithm.next_interval(o,d),y=Math.max(this.algorithm.next_interval(n.stability,d),f+1);n.scheduled_days=y,n.due=this.review_time.scheduler(y,!0),n.state=c.Review;break}default:throw new Error("Invalid grade")}const u={card:n,log:this.buildLog(t)};return this.next.set(t,u),u}reviewState(t){const e=this.next.get(t);if(e)return e;const i=this.current.elapsed_days,{difficulty:a,stability:r}=this.last,n=this.algorithm.forgetting_curve(i,r),d=h.card(this.current),u=h.card(this.current),o=h.card(this.current),f=h.card(this.current);this.next_ds(d,u,o,f,a,r,n),this.next_interval(d,u,o,f,i),this.next_state(d,u,o,f),d.lapses+=1;const y={card:d,log:this.buildLog(l.Again)},w={card:u,log:super.buildLog(l.Hard)},g={card:o,log:super.buildLog(l.Good)},M={card:f,log:super.buildLog(l.Easy)};return this.next.set(l.Again,y),this.next.set(l.Hard,w),this.next.set(l.Good,g),this.next.set(l.Easy,M),this.next.get(t)}next_ds(t,e,i,a,r,n,d){t.difficulty=this.algorithm.next_difficulty(r,l.Again);const u=n/Math.exp(this.algorithm.parameters.w[17]*this.algorithm.parameters.w[18]),o=this.algorithm.next_forget_stability(r,n,d);t.stability=m(+u.toFixed(8),_,o),e.difficulty=this.algorithm.next_difficulty(r,l.Hard),e.stability=this.algorithm.next_recall_stability(r,n,d,l.Hard),i.difficulty=this.algorithm.next_difficulty(r,l.Good),i.stability=this.algorithm.next_recall_stability(r,n,d,l.Good),a.difficulty=this.algorithm.next_difficulty(r,l.Easy),a.stability=this.algorithm.next_recall_stability(r,n,d,l.Easy)}next_interval(t,e,i,a,r){let n,d;n=this.algorithm.next_interval(e.stability,r),d=this.algorithm.next_interval(i.stability,r),n=Math.min(n,d),d=Math.max(d,n+1);const u=Math.max(this.algorithm.next_interval(a.stability,r),d+1);t.scheduled_days=0,t.due=this.review_time.scheduler(5),e.scheduled_days=n,e.due=this.review_time.scheduler(n,!0),i.scheduled_days=d,i.due=this.review_time.scheduler(d,!0),a.scheduled_days=u,a.due=this.review_time.scheduler(u,!0)}next_state(t,e,i,a){t.state=c.Relearning,e.state=c.Review,i.state=c.Review,a.state=c.Review}}class W extends H{newState(t){const e=this.next.get(t);if(e)return e;this.current.scheduled_days=0,this.current.elapsed_days=0;const i=h.card(this.current),a=h.card(this.current),r=h.card(this.current),n=h.card(this.current);return this.init_ds(i,a,r,n),this.next_interval(i,a,r,n,0),this.next_state(i,a,r,n),this.update_next(i,a,r,n),this.next.get(t)}init_ds(t,e,i,a){t.difficulty=this.algorithm.init_difficulty(l.Again),t.stability=this.algorithm.init_stability(l.Again),e.difficulty=this.algorithm.init_difficulty(l.Hard),e.stability=this.algorithm.init_stability(l.Hard),i.difficulty=this.algorithm.init_difficulty(l.Good),i.stability=this.algorithm.init_stability(l.Good),a.difficulty=this.algorithm.init_difficulty(l.Easy),a.stability=this.algorithm.init_stability(l.Easy)}learningState(t){return this.reviewState(t)}reviewState(t){const e=this.next.get(t);if(e)return e;const i=this.current.elapsed_days,{difficulty:a,stability:r}=this.last,n=this.algorithm.forgetting_curve(i,r),d=h.card(this.current),u=h.card(this.current),o=h.card(this.current),f=h.card(this.current);return this.next_ds(d,u,o,f,a,r,n),this.next_interval(d,u,o,f,i),this.next_state(d,u,o,f),d.lapses+=1,this.update_next(d,u,o,f),this.next.get(t)}next_ds(t,e,i,a,r,n,d){t.difficulty=this.algorithm.next_difficulty(r,l.Again);const u=this.algorithm.next_forget_stability(r,n,d);t.stability=m(n,_,u),e.difficulty=this.algorithm.next_difficulty(r,l.Hard),e.stability=this.algorithm.next_recall_stability(r,n,d,l.Hard),i.difficulty=this.algorithm.next_difficulty(r,l.Good),i.stability=this.algorithm.next_recall_stability(r,n,d,l.Good),a.difficulty=this.algorithm.next_difficulty(r,l.Easy),a.stability=this.algorithm.next_recall_stability(r,n,d,l.Easy)}next_interval(t,e,i,a,r){let n,d,u,o;n=this.algorithm.next_interval(t.stability,r),d=this.algorithm.next_interval(e.stability,r),u=this.algorithm.next_interval(i.stability,r),o=this.algorithm.next_interval(a.stability,r),n=Math.min(n,d),d=Math.max(d,n+1),u=Math.max(u,d+1),o=Math.max(o,u+1),t.scheduled_days=n,t.due=this.review_time.scheduler(n,!0),e.scheduled_days=d,e.due=this.review_time.scheduler(d,!0),i.scheduled_days=u,i.due=this.review_time.scheduler(u,!0),a.scheduled_days=o,a.due=this.review_time.scheduler(o,!0)}next_state(t,e,i,a){t.state=c.Review,e.state=c.Review,i.state=c.Review,a.state=c.Review}update_next(t,e,i,a){const r={card:t,log:this.buildLog(l.Again)},n={card:e,log:super.buildLog(l.Hard)},d={card:i,log:super.buildLog(l.Good)},u={card:a,log:super.buildLog(l.Easy)};this.next.set(l.Again,r),this.next.set(l.Hard,n),this.next.set(l.Good,d),this.next.set(l.Easy,u)}}class st{fsrs;constructor(t){this.fsrs=t}replay(t,e,i){return this.fsrs.next(t,e,i)}handleManualRating(t,e,i,a,r,n,d){if(typeof e>"u")throw new Error("reschedule: state is required for manual rating");let u,o;if(e===c.New)u={rating:l.Manual,state:e,due:d??i,stability:t.stability,difficulty:t.difficulty,elapsed_days:a,last_elapsed_days:t.elapsed_days,scheduled_days:t.scheduled_days,review:i},o=x(i),o.last_review=i;else{if(typeof d>"u")throw new Error("reschedule: due is required for manual rating");const f=d.diff(i,"days");u={rating:l.Manual,state:t.state,due:t.last_review||t.due,stability:t.stability,difficulty:t.difficulty,elapsed_days:a,last_elapsed_days:t.elapsed_days,scheduled_days:t.scheduled_days,review:i},o={...t,state:e,due:d,last_review:i,stability:r||t.stability,difficulty:n||t.difficulty,elapsed_days:a,scheduled_days:f,reps:t.reps+1}}return{card:o,log:u}}reschedule(t,e){const i=[];let a=x(t.due);for(const r of e){let n;if(r.review=h.time(r.review),r.rating===l.Manual){let d=0;a.state!==c.New&&a.last_review&&(d=r.review.diff(a.last_review,"days")),n=this.handleManualRating(a,r.state,r.review,d,r.stability,r.difficulty,r.due?h.time(r.due):void 0)}else n=this.replay(a,r.review,r.rating);i.push(n),a=n.card}return i}calculateManualRecord(t,e,i,a){if(!i)return null;const{card:r,log:n}=i,d=h.card(t);return d.due.getTime()===r.due.getTime()?null:(d.scheduled_days=r.due.diff(d.due,"days"),this.handleManualRating(d,r.state,h.time(e),n.elapsed_days,a?r.stability:void 0,a?r.difficulty:void 0,r.due))}}class X extends V{strategyHandler=new Map;Scheduler;constructor(t){super(t);const{enable_short_term:e}=this.parameters;this.Scheduler=e?j:W}params_handler_proxy(){const t=this;return{set:function(e,i,a){return i==="request_retention"&&Number.isFinite(a)?t.intervalModifier=t.calculate_interval_modifier(Number(a)):i==="enable_short_term"&&(t.Scheduler=a===!0?j:W),Reflect.set(e,i,a),!0}}}useStrategy(t,e){return this.strategyHandler.set(t,e),this}clearStrategy(t){return t?this.strategyHandler.delete(t):this.strategyHandler.clear(),this}getScheduler(t,e){const i=this.strategyHandler.get(b.SEED),a=this.strategyHandler.get(b.SCHEDULER)||this.Scheduler,r=i||$;return new a(t,e,this,{seed:r})}repeat(t,e,i){const a=this.getScheduler(t,e).preview();return i&&typeof i=="function"?i(a):a}next(t,e,i,a){const r=this.getScheduler(t,e),n=h.rating(i);if(n===l.Manual)throw new Error("Cannot review a manual rating");const d=r.review(n);return a&&typeof a=="function"?a(d):d}get_retrievability(t,e,i=!0){const a=h.card(t);e=e?h.time(e):new Date;const r=a.state!==c.New?Math.max(e.diff(a.last_review,"days"),0):0,n=a.state!==c.New?this.forgetting_curve(r,+a.stability.toFixed(8)):0;return i?`${(n*100).toFixed(2)}%`:n}rollback(t,e,i){const a=h.card(t),r=h.review_log(e);if(r.rating===l.Manual)throw new Error("Cannot rollback a manual rating");let n,d,u;switch(r.state){case c.New:n=r.due,d=void 0,u=0;break;case c.Learning:case c.Relearning:case c.Review:n=r.review,d=r.due,u=a.lapses-(r.rating===l.Again&&r.state===c.Review?1:0);break}const o={...a,due:n,stability:r.stability,difficulty:r.difficulty,elapsed_days:r.last_elapsed_days,scheduled_days:r.scheduled_days,reps:Math.max(0,a.reps-1),lapses:Math.max(0,u),state:r.state,last_review:d};return i&&typeof i=="function"?i(o):o}forget(t,e,i=!1,a){const r=h.card(t);e=h.time(e);const n=r.state===c.New?0:e.diff(r.last_review,"days"),d={rating:l.Manual,state:r.state,due:r.due,stability:r.stability,difficulty:r.difficulty,elapsed_days:0,last_elapsed_days:r.elapsed_days,scheduled_days:n,review:e},u={card:{...r,due:e,stability:0,difficulty:0,elapsed_days:0,scheduled_days:0,reps:i?0:r.reps,lapses:i?0:r.lapses,state:c.New,last_review:r.last_review},log:d};return a&&typeof a=="function"?a(u):u}reschedule(t,e=[],i={}){const{recordLogHandler:a,reviewsOrderBy:r,skipManual:n=!0,now:d=new Date,update_memory_state:u=!1}=i;r&&typeof r=="function"&&e.sort(r),n&&(e=e.filter(M=>M.rating!==l.Manual));const o=new st(this),f=o.reschedule(i.first_card||x(),e),y=f.length,w=h.card(t),g=o.calculateManualRecord(w,d,y?f[y-1]:void 0,u);return a&&typeof a=="function"?{collections:f.map(a),reschedule_item:g?a(g):null}:{collections:f,reschedule_item:g}}}const nt=s=>new X(s||{});exports.AbstractScheduler=H,exports.CLAMP_PARAMETERS=R,exports.DECAY=D,exports.DefaultInitSeedStrategy=$,exports.FACTOR=F,exports.FSRS=X,exports.FSRSAlgorithm=V,exports.FSRSVersion=tt,exports.GenSeedStrategyWithCardId=rt,exports.Grades=N,exports.INIT_S_MAX=v,exports.Rating=l,exports.S_MIN=_,exports.State=c,exports.StrategyMode=b,exports.TypeConvert=h,exports.clamp=m,exports.createEmptyCard=x,exports.dateDiffInDays=z,exports.date_diff=L,exports.date_scheduler=I,exports.default_enable_fuzz=P,exports.default_enable_short_term=Y,exports.default_maximum_interval=U,exports.default_request_retention=T,exports.default_w=q,exports.fixDate=J,exports.fixRating=Q,exports.fixState=K,exports.forgetting_curve=O,exports.formatDate=C,exports.fsrs=nt,exports.generatorParameters=A,exports.get_fuzz_range=k,exports.show_diff_message=G,module.exports=Object.assign(exports.default||{},exports);
|
|
1
|
+
"use strict";var h=(s=>(s[s.New=0]="New",s[s.Learning=1]="Learning",s[s.Review=2]="Review",s[s.Relearning=3]="Relearning",s))(h||{}),l=(s=>(s[s.Manual=0]="Manual",s[s.Again=1]="Again",s[s.Hard=2]="Hard",s[s.Good=3]="Good",s[s.Easy=4]="Easy",s))(l||{});class o{static card(t){return{...t,state:o.state(t.state),due:o.time(t.due),last_review:t.last_review?o.time(t.last_review):void 0}}static rating(t){if(typeof t=="string"){const e=t.charAt(0).toUpperCase(),i=t.slice(1).toLowerCase(),r=l[`${e}${i}`];if(r===void 0)throw new Error(`Invalid rating:[${t}]`);return r}else if(typeof t=="number")return t;throw new Error(`Invalid rating:[${t}]`)}static state(t){if(typeof t=="string"){const e=t.charAt(0).toUpperCase(),i=t.slice(1).toLowerCase(),r=h[`${e}${i}`];if(r===void 0)throw new Error(`Invalid state:[${t}]`);return r}else if(typeof t=="number")return t;throw new Error(`Invalid state:[${t}]`)}static time(t){if(typeof t=="object"&&t instanceof Date)return t;if(typeof t=="string"){const e=Date.parse(t);if(isNaN(e))throw new Error(`Invalid date:[${t}]`);return new Date(e)}else if(typeof t=="number")return new Date(t);throw new Error(`Invalid date:[${t}]`)}static review_log(t){return{...t,due:o.time(t.due),rating:o.rating(t.rating),state:o.state(t.state),review:o.time(t.review)}}}Date.prototype.scheduler=function(s,t){return H(this,s,t)},Date.prototype.diff=function(s,t){return C(this,s,t)},Date.prototype.format=function(){return G(this)},Date.prototype.dueFormat=function(s,t,e){return T(this,s,t,e)};function H(s,t,e){return new Date(e?o.time(s).getTime()+t*24*60*60*1e3:o.time(s).getTime()+t*60*1e3)}function C(s,t,e){if(!s||!t)throw new Error("Invalid date");const i=o.time(s).getTime()-o.time(t).getTime();let r=0;switch(e){case"days":r=Math.floor(i/(24*60*60*1e3));break;case"minutes":r=Math.floor(i/(60*1e3));break}return r}function G(s){const t=o.time(s),e=t.getFullYear(),i=t.getMonth()+1,r=t.getDate(),a=t.getHours(),n=t.getMinutes(),d=t.getSeconds();return`${e}-${v(i)}-${v(r)} ${v(a)}:${v(n)}:${v(d)}`}function v(s){return s<10?`0${s}`:`${s}`}const A=[60,60,24,31,12],F=["second","min","hour","day","month","year"];function T(s,t,e,i=F){s=o.time(s),t=o.time(t),i.length!==F.length&&(i=F);let r=s.getTime()-t.getTime(),a;for(r/=1e3,a=0;a<A.length&&!(r<A[a]);a++)r/=A[a];return`${Math.floor(r)}${e?i[a]:""}`}function rt(s){return o.time(s)}function at(s){return o.state(s)}function st(s){return o.rating(s)}const z=Object.freeze([l.Again,l.Hard,l.Good,l.Easy]),nt=[{start:2.5,end:7,factor:.15},{start:7,end:20,factor:.1},{start:20,end:1/0,factor:.05}];function U(s,t,e){let i=1;for(const n of nt)i+=n.factor*Math.max(Math.min(s,n.end)-n.start,0);s=Math.min(s,e);let r=Math.max(2,Math.round(s-i));const a=Math.min(Math.round(s+i),e);return s>t&&(r=Math.max(r,t+1)),r=Math.min(r,a),{min_ivl:r,max_ivl:a}}function y(s,t,e){return Math.min(Math.max(s,t),e)}function P(s,t){const e=Date.UTC(s.getUTCFullYear(),s.getUTCMonth(),s.getUTCDate()),i=Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate());return Math.floor((i-e)/864e5)}const lt="5.0.0",k=.9,q=36500,Y=!1,j=!0,O=Object.freeze(["1m","10m"]),W=Object.freeze(["10m"]),dt=`v${lt} using FSRS-6.0`,m=.001,ot=36500,x=100,D=.5,X=.2,L=Object.freeze([.2172,1.1771,3.2602,16.1507,7.0114,.57,2.0966,.0069,1.5261,.112,1.0178,1.849,.1133,.3127,2.2934,.2191,3.0004,.7536,.3332,.1437,X]),B=2,V=s=>[[m,x],[m,x],[m,x],[m,x],[1,10],[.001,4],[.001,4],[.001,.75],[0,4.5],[0,.8],[.001,3.5],[.001,5],[.001,.25],[.001,.9],[0,4],[0,1],[1,6],[0,s],[0,s],[0,.8],[.1,.8]],S=(s,t)=>{let e=B;if(Math.max(0,t)>1){const i=-(Math.log(s[11])+Math.log(Math.pow(2,s[13])-1)+s[14]*.3)/t;e=y(+i.toFixed(8),.01,2)}return V(e).map(([i,r],a)=>y(s[a],i,r))},ut=s=>{if(s.find(t=>!isFinite(t)&&!isNaN(t))!==void 0)throw Error(`Non-finite or NaN value in parameters ${s}`);if(![17,19,21].includes(s.length))throw Error(`Invalid parameter length: ${s.length}. Must be 17, 19 or 21 for FSRSv4, 5 and 6 respectively.`);return s},M=s=>{if(s===void 0)return[...L];switch(s.length){case 21:return[...s];case 19:return console.debug("[FSRS-6]auto fill w from 19 to 21 length"),[...s,0,D];case 17:{const t=[...s];return t[4]=+(t[5]*2+t[4]).toFixed(8),t[5]=+(Math.log(t[5]*3+1)/3).toFixed(8),t[6]=+(t[6]+.5).toFixed(8),console.debug("[FSRS-6]auto fill w from 17 to 21 length"),t.concat([0,0,0,D])}default:return console.warn("[FSRS]Invalid parameters length, using default parameters"),[...L]}},N=s=>{const t=Array.isArray(s?.learning_steps)?s.learning_steps:O,e=Array.isArray(s?.relearning_steps)?s.relearning_steps:W,i=S(M(s?.w),e.length);return{request_retention:s?.request_retention||k,maximum_interval:s?.maximum_interval||q,w:i,enable_fuzz:s?.enable_fuzz??Y,enable_short_term:s?.enable_short_term??j,learning_steps:t,relearning_steps:e}};function E(s,t){const e={due:s?o.time(s):new Date,stability:0,difficulty:0,elapsed_days:0,scheduled_days:0,reps:0,lapses:0,learning_steps:0,state:h.New,last_review:void 0};return t&&typeof t=="function"?t(e):e}class ht{c;s0;s1;s2;constructor(t){const e=ct();this.c=1,this.s0=e(" "),this.s1=e(" "),this.s2=e(" "),t==null&&(t=+new Date),this.s0-=e(t),this.s0<0&&(this.s0+=1),this.s1-=e(t),this.s1<0&&(this.s1+=1),this.s2-=e(t),this.s2<0&&(this.s2+=1)}next(){const t=2091639*this.s0+this.c*23283064365386963e-26;return this.s0=this.s1,this.s1=this.s2,this.s2=t-(this.c=t|0),this.s2}set state(t){this.c=t.c,this.s0=t.s0,this.s1=t.s1,this.s2=t.s2}get state(){return{c:this.c,s0:this.s0,s1:this.s1,s2:this.s2}}}function ct(){let s=4022871197;return function(t){t=String(t);for(let e=0;e<t.length;e++){s+=t.charCodeAt(e);let i=.02519603282416938*s;s=i>>>0,i-=s,i*=s,s=i>>>0,i-=s,s+=i*4294967296}return(s>>>0)*23283064365386963e-26}}function _t(s){const t=new ht(s),e=()=>t.next();return e.int32=()=>t.next()*4294967296|0,e.double=()=>e()+(e()*2097152|0)*11102230246251565e-32,e.state=()=>t.state,e.importState=i=>(t.state=i,e),e}const I=s=>{const t=typeof s=="number"?-s:-s[20],e=Math.exp(Math.pow(t,-1)*Math.log(.9))-1;return{decay:t,factor:+e.toFixed(8)}};function R(s,t,e){const{decay:i,factor:r}=I(s);return+Math.pow(1+r*t/e,i).toFixed(8)}class J{param;intervalModifier;_seed;constructor(t){this.param=new Proxy(N(t),this.params_handler_proxy()),this.intervalModifier=this.calculate_interval_modifier(this.param.request_retention),this.forgetting_curve=R.bind(this,this.param.w)}get interval_modifier(){return this.intervalModifier}set seed(t){this._seed=t}calculate_interval_modifier(t){if(t<=0||t>1)throw new Error("Requested retention rate should be in the range (0,1]");const{decay:e,factor:i}=I(this.param.w);return+((Math.pow(t,1/e)-1)/i).toFixed(8)}get parameters(){return this.param}set parameters(t){this.update_parameters(t)}params_handler_proxy(){const t=this;return{set:function(e,i,r){return i==="request_retention"&&Number.isFinite(r)?t.intervalModifier=t.calculate_interval_modifier(Number(r)):i==="w"&&(r=S(M(r),e.relearning_steps.length),t.forgetting_curve=R.bind(this,r),t.intervalModifier=t.calculate_interval_modifier(Number(e.request_retention))),Reflect.set(e,i,r),!0}}}update_parameters(t){const e=N(t);for(const i in e)if(i in this.param){const r=i;this.param[r]=e[r]}}init_stability(t){return Math.max(this.param.w[t-1],.1)}init_difficulty(t){const e=this.param.w[4]-Math.exp((t-1)*this.param.w[5])+1;return y(+e.toFixed(8),1,10)}apply_fuzz(t,e){if(!this.param.enable_fuzz||t<2.5)return Math.round(t);const i=_t(this._seed)(),{min_ivl:r,max_ivl:a}=U(t,e,this.param.maximum_interval);return Math.floor(i*(a-r+1)+r)}next_interval(t,e){const i=Math.min(Math.max(1,Math.round(t*this.intervalModifier)),this.param.maximum_interval);return this.apply_fuzz(i,e)}linear_damping(t,e){return+(t*(10-e)/9).toFixed(8)}next_difficulty(t,e){const i=-this.param.w[6]*(e-3),r=t+this.linear_damping(i,t);return y(this.mean_reversion(this.init_difficulty(l.Easy),r),1,10)}mean_reversion(t,e){return+(this.param.w[7]*t+(1-this.param.w[7])*e).toFixed(8)}next_recall_stability(t,e,i,r){const a=l.Hard===r?this.param.w[15]:1,n=l.Easy===r?this.param.w[16]:1;return+y(e*(1+Math.exp(this.param.w[8])*(11-t)*Math.pow(e,-this.param.w[9])*(Math.exp((1-i)*this.param.w[10])-1)*a*n),m,36500).toFixed(8)}next_forget_stability(t,e,i){return+y(this.param.w[11]*Math.pow(t,-this.param.w[12])*(Math.pow(e+1,this.param.w[13])-1)*Math.exp((1-i)*this.param.w[14]),m,36500).toFixed(8)}next_short_term_stability(t,e){const i=Math.pow(t,-this.param.w[19])*Math.exp(this.param.w[17]*(e-3+this.param.w[18])),r=e>=3?Math.max(i,1):i;return+y(t*r,m,36500).toFixed(8)}forgetting_curve;next_state(t,e,i){const{difficulty:r,stability:a}=t??{difficulty:0,stability:0};if(e<0)throw new Error(`Invalid delta_t "${e}"`);if(i<0||i>4)throw new Error(`Invalid grade "${i}"`);if(r===0&&a===0)return{difficulty:this.init_difficulty(i),stability:this.init_stability(i)};if(i===0)return{difficulty:r,stability:a};if(r<1||a<m)throw new Error(`Invalid memory state { difficulty: ${r}, stability: ${a} }`);const n=this.forgetting_curve(e,a),d=this.next_recall_stability(r,a,n,i),u=this.next_forget_stability(r,a,n),c=this.next_short_term_stability(a,i);let _=d;if(i===1){let[g,p]=[0,0];this.param.enable_short_term&&(g=this.param.w[17],p=this.param.w[18]);const f=a/Math.exp(g*p);_=y(+f.toFixed(8),m,u)}return e===0&&this.param.enable_short_term&&(_=c),{difficulty:this.next_difficulty(r,i),stability:_}}}function K(){const s=this.review_time.getTime(),t=this.current.reps,e=this.current.difficulty*this.current.stability;return`${s}_${t}_${e}`}function ft(s){return function(){const t=Reflect.get(this.current,s)??0,e=this.current.reps;return String(t+e||0)}}const Q=s=>{const t=s.slice(-1),e=parseInt(s.slice(0,-1),10);if(isNaN(e)||!Number.isFinite(e)||e<0)throw new Error(`Invalid step value: ${s}`);switch(t){case"m":return e;case"h":return e*60;case"d":return e*1440;default:throw new Error(`Invalid step unit: ${s}, expected m/h/d`)}},Z=(s,t,e)=>{const i=t===h.Relearning||t===h.Review?s.relearning_steps:s.learning_steps,r=i.length;if(r===0||e>=r)return{};const a=i[0],n=Q,d=()=>n(a),u=()=>{if(r===1)return Math.round(n(a)*1.5);const f=i[1];return Math.round((n(a)+n(f))/2)},c=f=>f<0||f>=r?null:i[f],_=f=>n(f),g={},p=c(Math.max(0,e));if(t===h.Review)return g[l.Again]={scheduled_minutes:n(p),next_step:0},g;{g[l.Again]={scheduled_minutes:d(),next_step:0},g[l.Hard]={scheduled_minutes:u(),next_step:e};const f=c(e+1);if(f){const w=_(f);w&&(g[l.Good]={scheduled_minutes:Math.round(w),next_step:e+1})}}return g};var b=(s=>(s.SCHEDULER="Scheduler",s.LEARNING_STEPS="LearningSteps",s.SEED="Seed",s))(b||{});class ${last;current;review_time;next=new Map;algorithm;strategies;constructor(t,e,i,r){this.algorithm=i,this.last=o.card(t),this.current=o.card(t),this.review_time=o.time(e),this.strategies=r,this.init()}checkGrade(t){if(!Number.isFinite(t)||t<0||t>4)throw new Error(`Invalid grade "${t}",expected 1-4`)}init(){const{state:t,last_review:e}=this.current;let i=0;t!==h.New&&e&&(i=P(e,this.review_time)),this.current.last_review=this.review_time,this.current.elapsed_days=i,this.current.reps+=1;let r=K;if(this.strategies){const a=this.strategies.get(b.SEED);a&&(r=a)}this.algorithm.seed=r.call(this)}preview(){return{[l.Again]:this.review(l.Again),[l.Hard]:this.review(l.Hard),[l.Good]:this.review(l.Good),[l.Easy]:this.review(l.Easy),[Symbol.iterator]:this.previewIterator.bind(this)}}*previewIterator(){for(const t of z)yield this.review(t)}review(t){const{state:e}=this.last;let i;switch(this.checkGrade(t),e){case h.New:i=this.newState(t);break;case h.Learning:case h.Relearning:i=this.learningState(t);break;case h.Review:i=this.reviewState(t);break}return i}buildLog(t){const{last_review:e,due:i,elapsed_days:r}=this.last;return{rating:t,state:this.current.state,due:e||i,stability:this.current.stability,difficulty:this.current.difficulty,elapsed_days:this.current.elapsed_days,last_elapsed_days:r,scheduled_days:this.current.scheduled_days,learning_steps:this.current.learning_steps,review:this.review_time}}}class tt extends ${learningStepsStrategy;constructor(t,e,i,r){super(t,e,i,r);let a=Z;if(this.strategies){const n=this.strategies.get(b.LEARNING_STEPS);n&&(a=n)}this.learningStepsStrategy=a}getLearningInfo(t,e){const i=this.algorithm.parameters;t.learning_steps=t.learning_steps||0;const r=this.learningStepsStrategy(i,t.state,this.current.state===h.Learning?t.learning_steps+1:t.learning_steps),a=Math.max(0,r[e]?.scheduled_minutes??0),n=Math.max(0,r[e]?.next_step??0);return{scheduled_minutes:a,next_steps:n}}applyLearningSteps(t,e,i){const{scheduled_minutes:r,next_steps:a}=this.getLearningInfo(this.current,e);if(r>0&&r<1440)t.learning_steps=a,t.scheduled_days=0,t.state=i,t.due=this.review_time.scheduler(Math.round(r),!1);else if(t.state=h.Review,r>=1440)t.learning_steps=a,t.due=this.review_time.scheduler(Math.round(r),!1),t.scheduled_days=Math.floor(r/1440);else{t.learning_steps=0;const n=this.algorithm.next_interval(t.stability,this.current.elapsed_days);t.scheduled_days=n,t.due=this.review_time.scheduler(n,!0)}}newState(t){const e=this.next.get(t);if(e)return e;const i=o.card(this.current);i.difficulty=this.algorithm.init_difficulty(t),i.stability=this.algorithm.init_stability(t),this.applyLearningSteps(i,t,h.Learning);const r={card:i,log:this.buildLog(t)};return this.next.set(t,r),r}learningState(t){const e=this.next.get(t);if(e)return e;const{state:i,difficulty:r,stability:a}=this.last,n=o.card(this.current);n.difficulty=this.algorithm.next_difficulty(r,t),n.stability=this.algorithm.next_short_term_stability(a,t),this.applyLearningSteps(n,t,i);const d={card:n,log:this.buildLog(t)};return this.next.set(t,d),d}reviewState(t){const e=this.next.get(t);if(e)return e;const i=this.current.elapsed_days,{difficulty:r,stability:a}=this.last,n=this.algorithm.forgetting_curve(i,a),d=o.card(this.current),u=o.card(this.current),c=o.card(this.current),_=o.card(this.current);this.next_ds(d,u,c,_,r,a,n),this.next_interval(u,c,_,i),this.next_state(u,c,_),this.applyLearningSteps(d,l.Again,h.Relearning),d.lapses+=1;const g={card:d,log:this.buildLog(l.Again)},p={card:u,log:super.buildLog(l.Hard)},f={card:c,log:super.buildLog(l.Good)},w={card:_,log:super.buildLog(l.Easy)};return this.next.set(l.Again,g),this.next.set(l.Hard,p),this.next.set(l.Good,f),this.next.set(l.Easy,w),this.next.get(t)}next_ds(t,e,i,r,a,n,d){t.difficulty=this.algorithm.next_difficulty(a,l.Again);const u=n/Math.exp(this.algorithm.parameters.w[17]*this.algorithm.parameters.w[18]),c=this.algorithm.next_forget_stability(a,n,d);t.stability=y(+u.toFixed(8),m,c),e.difficulty=this.algorithm.next_difficulty(a,l.Hard),e.stability=this.algorithm.next_recall_stability(a,n,d,l.Hard),i.difficulty=this.algorithm.next_difficulty(a,l.Good),i.stability=this.algorithm.next_recall_stability(a,n,d,l.Good),r.difficulty=this.algorithm.next_difficulty(a,l.Easy),r.stability=this.algorithm.next_recall_stability(a,n,d,l.Easy)}next_interval(t,e,i,r){let a,n;a=this.algorithm.next_interval(t.stability,r),n=this.algorithm.next_interval(e.stability,r),a=Math.min(a,n),n=Math.max(n,a+1);const d=Math.max(this.algorithm.next_interval(i.stability,r),n+1);t.scheduled_days=a,t.due=this.review_time.scheduler(a,!0),e.scheduled_days=n,e.due=this.review_time.scheduler(n,!0),i.scheduled_days=d,i.due=this.review_time.scheduler(d,!0)}next_state(t,e,i){t.state=h.Review,t.learning_steps=0,e.state=h.Review,e.learning_steps=0,i.state=h.Review,i.learning_steps=0}}class et extends ${newState(t){const e=this.next.get(t);if(e)return e;this.current.scheduled_days=0,this.current.elapsed_days=0;const i=o.card(this.current),r=o.card(this.current),a=o.card(this.current),n=o.card(this.current);return this.init_ds(i,r,a,n),this.next_interval(i,r,a,n,0),this.next_state(i,r,a,n),this.update_next(i,r,a,n),this.next.get(t)}init_ds(t,e,i,r){t.difficulty=this.algorithm.init_difficulty(l.Again),t.stability=this.algorithm.init_stability(l.Again),e.difficulty=this.algorithm.init_difficulty(l.Hard),e.stability=this.algorithm.init_stability(l.Hard),i.difficulty=this.algorithm.init_difficulty(l.Good),i.stability=this.algorithm.init_stability(l.Good),r.difficulty=this.algorithm.init_difficulty(l.Easy),r.stability=this.algorithm.init_stability(l.Easy)}learningState(t){return this.reviewState(t)}reviewState(t){const e=this.next.get(t);if(e)return e;const i=this.current.elapsed_days,{difficulty:r,stability:a}=this.last,n=this.algorithm.forgetting_curve(i,a),d=o.card(this.current),u=o.card(this.current),c=o.card(this.current),_=o.card(this.current);return this.next_ds(d,u,c,_,r,a,n),this.next_interval(d,u,c,_,i),this.next_state(d,u,c,_),d.lapses+=1,this.update_next(d,u,c,_),this.next.get(t)}next_ds(t,e,i,r,a,n,d){t.difficulty=this.algorithm.next_difficulty(a,l.Again);const u=this.algorithm.next_forget_stability(a,n,d);t.stability=y(n,m,u),e.difficulty=this.algorithm.next_difficulty(a,l.Hard),e.stability=this.algorithm.next_recall_stability(a,n,d,l.Hard),i.difficulty=this.algorithm.next_difficulty(a,l.Good),i.stability=this.algorithm.next_recall_stability(a,n,d,l.Good),r.difficulty=this.algorithm.next_difficulty(a,l.Easy),r.stability=this.algorithm.next_recall_stability(a,n,d,l.Easy)}next_interval(t,e,i,r,a){let n,d,u,c;n=this.algorithm.next_interval(t.stability,a),d=this.algorithm.next_interval(e.stability,a),u=this.algorithm.next_interval(i.stability,a),c=this.algorithm.next_interval(r.stability,a),n=Math.min(n,d),d=Math.max(d,n+1),u=Math.max(u,d+1),c=Math.max(c,u+1),t.scheduled_days=n,t.due=this.review_time.scheduler(n,!0),e.scheduled_days=d,e.due=this.review_time.scheduler(d,!0),i.scheduled_days=u,i.due=this.review_time.scheduler(u,!0),r.scheduled_days=c,r.due=this.review_time.scheduler(c,!0)}next_state(t,e,i,r){t.state=h.Review,t.learning_steps=0,e.state=h.Review,e.learning_steps=0,i.state=h.Review,i.learning_steps=0,r.state=h.Review,r.learning_steps=0}update_next(t,e,i,r){const a={card:t,log:this.buildLog(l.Again)},n={card:e,log:super.buildLog(l.Hard)},d={card:i,log:super.buildLog(l.Good)},u={card:r,log:super.buildLog(l.Easy)};this.next.set(l.Again,a),this.next.set(l.Hard,n),this.next.set(l.Good,d),this.next.set(l.Easy,u)}}class gt{fsrs;constructor(t){this.fsrs=t}replay(t,e,i){return this.fsrs.next(t,e,i)}handleManualRating(t,e,i,r,a,n,d){if(typeof e>"u")throw new Error("reschedule: state is required for manual rating");let u,c;if(e===h.New)u={rating:l.Manual,state:e,due:d??i,stability:t.stability,difficulty:t.difficulty,elapsed_days:r,last_elapsed_days:t.elapsed_days,scheduled_days:t.scheduled_days,learning_steps:t.learning_steps,review:i},c=E(i),c.last_review=i;else{if(typeof d>"u")throw new Error("reschedule: due is required for manual rating");const _=d.diff(i,"days");u={rating:l.Manual,state:t.state,due:t.last_review||t.due,stability:t.stability,difficulty:t.difficulty,elapsed_days:r,last_elapsed_days:t.elapsed_days,scheduled_days:t.scheduled_days,learning_steps:t.learning_steps,review:i},c={...t,state:e,due:d,last_review:i,stability:a||t.stability,difficulty:n||t.difficulty,elapsed_days:r,scheduled_days:_,reps:t.reps+1}}return{card:c,log:u}}reschedule(t,e){const i=[];let r=E(t.due);for(const a of e){let n;if(a.review=o.time(a.review),a.rating===l.Manual){let d=0;r.state!==h.New&&r.last_review&&(d=a.review.diff(r.last_review,"days")),n=this.handleManualRating(r,a.state,a.review,d,a.stability,a.difficulty,a.due?o.time(a.due):void 0)}else n=this.replay(r,a.review,a.rating);i.push(n),r=n.card}return i}calculateManualRecord(t,e,i,r){if(!i)return null;const{card:a,log:n}=i,d=o.card(t);return d.due.getTime()===a.due.getTime()?null:(d.scheduled_days=a.due.diff(d.due,"days"),this.handleManualRating(d,a.state,o.time(e),n.elapsed_days,r?a.stability:void 0,r?a.difficulty:void 0,a.due))}}class it extends J{strategyHandler=new Map;Scheduler;constructor(t){super(t);const{enable_short_term:e}=this.parameters;this.Scheduler=e?tt:et}params_handler_proxy(){const t=this;return{set:function(e,i,r){return i==="request_retention"&&Number.isFinite(r)?t.intervalModifier=t.calculate_interval_modifier(Number(r)):i==="enable_short_term"?t.Scheduler=r===!0?tt:et:i==="w"&&(r=S(M(r),e.relearning_steps.length),t.forgetting_curve=R.bind(this,r),t.intervalModifier=t.calculate_interval_modifier(Number(e.request_retention))),Reflect.set(e,i,r),!0}}}useStrategy(t,e){return this.strategyHandler.set(t,e),this}clearStrategy(t){return t?this.strategyHandler.delete(t):this.strategyHandler.clear(),this}getScheduler(t,e){const i=this.strategyHandler.get(b.SCHEDULER)||this.Scheduler;return new i(t,e,this,this.strategyHandler)}repeat(t,e,i){const r=this.getScheduler(t,e).preview();return i&&typeof i=="function"?i(r):r}next(t,e,i,r){const a=this.getScheduler(t,e),n=o.rating(i);if(n===l.Manual)throw new Error("Cannot review a manual rating");const d=a.review(n);return r&&typeof r=="function"?r(d):d}get_retrievability(t,e,i=!0){const r=o.card(t);e=e?o.time(e):new Date;const a=r.state!==h.New?Math.max(e.diff(r.last_review,"days"),0):0,n=r.state!==h.New?this.forgetting_curve(a,+r.stability.toFixed(8)):0;return i?`${(n*100).toFixed(2)}%`:n}rollback(t,e,i){const r=o.card(t),a=o.review_log(e);if(a.rating===l.Manual)throw new Error("Cannot rollback a manual rating");let n,d,u;switch(a.state){case h.New:n=a.due,d=void 0,u=0;break;case h.Learning:case h.Relearning:case h.Review:n=a.review,d=a.due,u=r.lapses-(a.rating===l.Again&&a.state===h.Review?1:0);break}const c={...r,due:n,stability:a.stability,difficulty:a.difficulty,elapsed_days:a.last_elapsed_days,scheduled_days:a.scheduled_days,reps:Math.max(0,r.reps-1),lapses:Math.max(0,u),learning_steps:a.learning_steps,state:a.state,last_review:d};return i&&typeof i=="function"?i(c):c}forget(t,e,i=!1,r){const a=o.card(t);e=o.time(e);const n=a.state===h.New?0:e.diff(a.last_review,"days"),d={rating:l.Manual,state:a.state,due:a.due,stability:a.stability,difficulty:a.difficulty,elapsed_days:0,last_elapsed_days:a.elapsed_days,scheduled_days:n,learning_steps:a.learning_steps,review:e},u={card:{...a,due:e,stability:0,difficulty:0,elapsed_days:0,scheduled_days:0,reps:i?0:a.reps,lapses:i?0:a.lapses,learning_steps:0,state:h.New,last_review:a.last_review},log:d};return r&&typeof r=="function"?r(u):u}reschedule(t,e=[],i={}){const{recordLogHandler:r,reviewsOrderBy:a,skipManual:n=!0,now:d=new Date,update_memory_state:u=!1}=i;a&&typeof a=="function"&&e.sort(a),n&&(e=e.filter(w=>w.rating!==l.Manual));const c=new gt(this),_=c.reschedule(i.first_card||E(),e),g=_.length,p=o.card(t),f=c.calculateManualRecord(p,d,g?_[g-1]:void 0,u);return r&&typeof r=="function"?{collections:_.map(r),reschedule_item:f?r(f):null}:{collections:_,reschedule_item:f}}}const mt=s=>new it(s||{});exports.AbstractScheduler=$,exports.BasicLearningStepsStrategy=Z,exports.CLAMP_PARAMETERS=V,exports.ConvertStepUnitToMinutes=Q,exports.DefaultInitSeedStrategy=K,exports.FSRS=it,exports.FSRS5_DEFAULT_DECAY=D,exports.FSRS6_DEFAULT_DECAY=X,exports.FSRSAlgorithm=J,exports.FSRSVersion=dt,exports.GenSeedStrategyWithCardId=ft,exports.Grades=z,exports.INIT_S_MAX=x,exports.Rating=l,exports.S_MAX=ot,exports.S_MIN=m,exports.State=h,exports.StrategyMode=b,exports.TypeConvert=o,exports.W17_W18_Ceiling=B,exports.checkParameters=ut,exports.clamp=y,exports.clipParameters=S,exports.computeDecayFactor=I,exports.createEmptyCard=E,exports.dateDiffInDays=P,exports.date_diff=C,exports.date_scheduler=H,exports.default_enable_fuzz=Y,exports.default_enable_short_term=j,exports.default_learning_steps=O,exports.default_maximum_interval=q,exports.default_relearning_steps=W,exports.default_request_retention=k,exports.default_w=L,exports.fixDate=rt,exports.fixRating=st,exports.fixState=at,exports.forgetting_curve=R,exports.formatDate=G,exports.fsrs=mt,exports.generatorParameters=N,exports.get_fuzz_range=U,exports.migrateParameters=M,exports.show_diff_message=T,module.exports=Object.assign(exports.default||{},exports);
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|