ts-fsrs 5.3.2 → 5.4.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/CHANGELOG.md +481 -0
- package/dist/index.cjs +67 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +67 -59
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +69 -59
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.umd.js
CHANGED
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.FSRS = {}));
|
|
5
5
|
})(this, (function (exports) { 'use strict';
|
|
6
6
|
|
|
7
|
+
class FSRSError extends Error {
|
|
8
|
+
constructor(message = "FSRS Error") {
|
|
9
|
+
var _a;
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "FSRSError";
|
|
12
|
+
(_a = Error.captureStackTrace) == null ? void 0 : _a.call(Error, this, FSRSError);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
class FSRSValidationError extends FSRSError {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
var _a;
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "FSRSValidationError";
|
|
20
|
+
(_a = Error.captureStackTrace) == null ? void 0 : _a.call(Error, this, FSRSValidationError);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
var State = /* @__PURE__ */ ((State2) => {
|
|
8
25
|
State2[State2["New"] = 0] = "New";
|
|
9
26
|
State2[State2["Learning"] = 1] = "Learning";
|
|
@@ -53,13 +70,13 @@
|
|
|
53
70
|
const restOfString = value.slice(1).toLowerCase();
|
|
54
71
|
const ret = Rating[`${firstLetter}${restOfString}`];
|
|
55
72
|
if (ret === void 0) {
|
|
56
|
-
throw new
|
|
73
|
+
throw new FSRSValidationError(`Invalid rating:[${value}]`);
|
|
57
74
|
}
|
|
58
75
|
return ret;
|
|
59
76
|
} else if (typeof value === "number") {
|
|
60
77
|
return value;
|
|
61
78
|
}
|
|
62
|
-
throw new
|
|
79
|
+
throw new FSRSValidationError(`Invalid rating:[${value}]`);
|
|
63
80
|
}
|
|
64
81
|
static state(value) {
|
|
65
82
|
if (typeof value === "string") {
|
|
@@ -67,13 +84,13 @@
|
|
|
67
84
|
const restOfString = value.slice(1).toLowerCase();
|
|
68
85
|
const ret = State[`${firstLetter}${restOfString}`];
|
|
69
86
|
if (ret === void 0) {
|
|
70
|
-
throw new
|
|
87
|
+
throw new FSRSValidationError(`Invalid state:[${value}]`);
|
|
71
88
|
}
|
|
72
89
|
return ret;
|
|
73
90
|
} else if (typeof value === "number") {
|
|
74
91
|
return value;
|
|
75
92
|
}
|
|
76
|
-
throw new
|
|
93
|
+
throw new FSRSValidationError(`Invalid state:[${value}]`);
|
|
77
94
|
}
|
|
78
95
|
static time(value) {
|
|
79
96
|
if (value instanceof Date) {
|
|
@@ -87,12 +104,12 @@
|
|
|
87
104
|
if (!Number.isNaN(timestamp)) {
|
|
88
105
|
return new Date(timestamp);
|
|
89
106
|
} else {
|
|
90
|
-
throw new
|
|
107
|
+
throw new FSRSValidationError(`Invalid date:[${value}]`);
|
|
91
108
|
}
|
|
92
109
|
} else if (typeof value === "number") {
|
|
93
110
|
return new Date(value);
|
|
94
111
|
}
|
|
95
|
-
throw new
|
|
112
|
+
throw new FSRSValidationError(`Invalid date:[${value}]`);
|
|
96
113
|
}
|
|
97
114
|
static review_log(log) {
|
|
98
115
|
return __spreadProps$2(__spreadValues$2({}, log), {
|
|
@@ -127,7 +144,7 @@
|
|
|
127
144
|
}
|
|
128
145
|
function date_diff(now, pre, unit) {
|
|
129
146
|
if (!now || !pre) {
|
|
130
|
-
throw new
|
|
147
|
+
throw new FSRSValidationError("Invalid date");
|
|
131
148
|
}
|
|
132
149
|
const diff = TypeConvert.time(now).getTime() - TypeConvert.time(pre).getTime();
|
|
133
150
|
let r = 0;
|
|
@@ -253,7 +270,7 @@
|
|
|
253
270
|
const unit = step.slice(-1);
|
|
254
271
|
const value = parseInt(step.slice(0, -1), 10);
|
|
255
272
|
if (Number.isNaN(value) || !Number.isFinite(value) || value < 0) {
|
|
256
|
-
throw new
|
|
273
|
+
throw new FSRSValidationError(`Invalid step value: ${step}`);
|
|
257
274
|
}
|
|
258
275
|
switch (unit) {
|
|
259
276
|
case "m":
|
|
@@ -263,7 +280,9 @@
|
|
|
263
280
|
case "d":
|
|
264
281
|
return value * 1440;
|
|
265
282
|
default:
|
|
266
|
-
throw new
|
|
283
|
+
throw new FSRSValidationError(
|
|
284
|
+
`Invalid step unit: ${step}, expected m/h/d`
|
|
285
|
+
);
|
|
267
286
|
}
|
|
268
287
|
};
|
|
269
288
|
const BasicLearningStepsStrategy = (params, state, cur_step) => {
|
|
@@ -364,8 +383,8 @@
|
|
|
364
383
|
this.init();
|
|
365
384
|
}
|
|
366
385
|
checkGrade(grade) {
|
|
367
|
-
if (!Number.isFinite(grade) || grade <
|
|
368
|
-
throw new
|
|
386
|
+
if (!Number.isFinite(grade) || grade < 1 || grade > 4) {
|
|
387
|
+
throw new FSRSValidationError(`Invalid grade "${grade}",expected 1-4`);
|
|
369
388
|
}
|
|
370
389
|
}
|
|
371
390
|
init() {
|
|
@@ -511,7 +530,7 @@
|
|
|
511
530
|
return prng;
|
|
512
531
|
}
|
|
513
532
|
|
|
514
|
-
const version="5.
|
|
533
|
+
const version="5.4.0";
|
|
515
534
|
|
|
516
535
|
const default_request_retention = 0.9;
|
|
517
536
|
const default_maximum_interval = 36500;
|
|
@@ -582,27 +601,31 @@
|
|
|
582
601
|
];
|
|
583
602
|
|
|
584
603
|
const clipParameters = (parameters, numRelearningSteps, enableShortTerm = default_enable_short_term) => {
|
|
585
|
-
|
|
586
|
-
if (Math.max(0, numRelearningSteps) > 1) {
|
|
587
|
-
const value = -(Math.log(parameters[11]) + Math.log(Math.pow(2, parameters[13]) - 1) + parameters[14] * 0.3) / numRelearningSteps;
|
|
588
|
-
w17_w18_ceiling = clamp(+value.toFixed(8), 0.01, 2);
|
|
589
|
-
}
|
|
590
|
-
const clip = CLAMP_PARAMETERS(w17_w18_ceiling, enableShortTerm).slice(
|
|
604
|
+
const clip = CLAMP_PARAMETERS(W17_W18_Ceiling, enableShortTerm).slice(
|
|
591
605
|
0,
|
|
592
606
|
parameters.length
|
|
593
607
|
);
|
|
608
|
+
if (Math.max(0, numRelearningSteps) > 1) {
|
|
609
|
+
const w11 = clamp(parameters[11] || 0, clip[11][0], clip[11][1]);
|
|
610
|
+
const w13 = clamp(parameters[13] || 0, clip[13][0], clip[13][1]);
|
|
611
|
+
const w14 = clamp(parameters[14] || 0, clip[14][0], clip[14][1]);
|
|
612
|
+
const value = -(Math.log(w11) + Math.log(Math.pow(2, w13) - 1) + w14 * 0.3) / numRelearningSteps;
|
|
613
|
+
const w17_w18_ceiling = clamp(roundTo(value, 8), 0.01, W17_W18_Ceiling);
|
|
614
|
+
if (clip[17]) clip[17] = [clip[17][0], w17_w18_ceiling];
|
|
615
|
+
if (clip[18]) clip[18] = [clip[18][0], w17_w18_ceiling];
|
|
616
|
+
}
|
|
594
617
|
return clip.map(
|
|
595
618
|
([min, max], index) => clamp(parameters[index] || 0, min, max)
|
|
596
619
|
);
|
|
597
620
|
};
|
|
598
621
|
const checkParameters = (parameters) => {
|
|
599
|
-
const invalid = parameters.find(
|
|
600
|
-
(param) => !Number.isFinite(param) && !Number.isNaN(param)
|
|
601
|
-
);
|
|
622
|
+
const invalid = parameters.find((param) => !Number.isFinite(param));
|
|
602
623
|
if (invalid !== void 0) {
|
|
603
|
-
throw
|
|
624
|
+
throw new FSRSValidationError(
|
|
625
|
+
`Non-finite or NaN value in parameters ${parameters}`
|
|
626
|
+
);
|
|
604
627
|
} else if (![17, 19, 21].includes(parameters.length)) {
|
|
605
|
-
throw
|
|
628
|
+
throw new FSRSValidationError(
|
|
606
629
|
`Invalid parameter length: ${parameters.length}. Must be 17, 19 or 21 for FSRSv4, 5 and 6 respectively.`
|
|
607
630
|
);
|
|
608
631
|
}
|
|
@@ -732,7 +755,9 @@
|
|
|
732
755
|
*/
|
|
733
756
|
calculate_interval_modifier(request_retention) {
|
|
734
757
|
if (request_retention <= 0 || request_retention > 1) {
|
|
735
|
-
throw new
|
|
758
|
+
throw new FSRSValidationError(
|
|
759
|
+
"Requested retention rate should be in the range (0,1]"
|
|
760
|
+
);
|
|
736
761
|
}
|
|
737
762
|
const { decay, factor } = computeDecayFactor(this.param.w);
|
|
738
763
|
return roundTo((Math.pow(request_retention, 1 / decay) - 1) / factor, 8);
|
|
@@ -940,10 +965,10 @@
|
|
|
940
965
|
stability: 0
|
|
941
966
|
};
|
|
942
967
|
if (t < 0) {
|
|
943
|
-
throw new
|
|
968
|
+
throw new FSRSValidationError(`Invalid delta_t "${t}"`);
|
|
944
969
|
}
|
|
945
970
|
if (g < 0 || g > 4) {
|
|
946
|
-
throw new
|
|
971
|
+
throw new FSRSValidationError(`Invalid grade "${g}"`);
|
|
947
972
|
}
|
|
948
973
|
if (d === 0 && s === 0) {
|
|
949
974
|
return {
|
|
@@ -958,7 +983,7 @@
|
|
|
958
983
|
};
|
|
959
984
|
}
|
|
960
985
|
if (d < 1 || s < S_MIN) {
|
|
961
|
-
throw new
|
|
986
|
+
throw new FSRSValidationError(
|
|
962
987
|
`Invalid memory state { difficulty: ${d}, stability: ${s} }`
|
|
963
988
|
);
|
|
964
989
|
}
|
|
@@ -1363,7 +1388,9 @@
|
|
|
1363
1388
|
*/
|
|
1364
1389
|
handleManualRating(card, state, reviewed, elapsed_days, stability, difficulty, due) {
|
|
1365
1390
|
if (typeof state === "undefined") {
|
|
1366
|
-
throw new
|
|
1391
|
+
throw new FSRSValidationError(
|
|
1392
|
+
"reschedule: state is required for manual rating"
|
|
1393
|
+
);
|
|
1367
1394
|
}
|
|
1368
1395
|
let log;
|
|
1369
1396
|
let next_card;
|
|
@@ -1384,7 +1411,9 @@
|
|
|
1384
1411
|
next_card.last_review = reviewed;
|
|
1385
1412
|
} else {
|
|
1386
1413
|
if (typeof due === "undefined") {
|
|
1387
|
-
throw new
|
|
1414
|
+
throw new FSRSValidationError(
|
|
1415
|
+
"reschedule: due is required for manual rating"
|
|
1416
|
+
);
|
|
1388
1417
|
}
|
|
1389
1418
|
const scheduled_days = date_diff(due, reviewed, "days");
|
|
1390
1419
|
log = {
|
|
@@ -1493,6 +1522,9 @@
|
|
|
1493
1522
|
};
|
|
1494
1523
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
1495
1524
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1525
|
+
function applyAfterHandler(value, afterHandler) {
|
|
1526
|
+
return typeof afterHandler === "function" ? afterHandler(value) : value;
|
|
1527
|
+
}
|
|
1496
1528
|
class FSRS extends FSRSAlgorithm {
|
|
1497
1529
|
constructor(param) {
|
|
1498
1530
|
super(param);
|
|
@@ -1608,11 +1640,7 @@
|
|
|
1608
1640
|
repeat(card, now, afterHandler) {
|
|
1609
1641
|
const instance = this.getScheduler(card, now);
|
|
1610
1642
|
const recordLog = instance.preview();
|
|
1611
|
-
|
|
1612
|
-
return afterHandler(recordLog);
|
|
1613
|
-
} else {
|
|
1614
|
-
return recordLog;
|
|
1615
|
-
}
|
|
1643
|
+
return applyAfterHandler(recordLog, afterHandler);
|
|
1616
1644
|
}
|
|
1617
1645
|
/**
|
|
1618
1646
|
* Display the collection of cards and logs for the card scheduled at the current time, after applying a specific grade rating.
|
|
@@ -1672,14 +1700,10 @@
|
|
|
1672
1700
|
const instance = this.getScheduler(card, now);
|
|
1673
1701
|
const g = TypeConvert.rating(grade);
|
|
1674
1702
|
if (g === Rating.Manual) {
|
|
1675
|
-
throw new
|
|
1703
|
+
throw new FSRSValidationError("Cannot review a manual rating");
|
|
1676
1704
|
}
|
|
1677
1705
|
const recordLogItem = instance.review(g);
|
|
1678
|
-
|
|
1679
|
-
return afterHandler(recordLogItem);
|
|
1680
|
-
} else {
|
|
1681
|
-
return recordLogItem;
|
|
1682
|
-
}
|
|
1706
|
+
return applyAfterHandler(recordLogItem, afterHandler);
|
|
1683
1707
|
}
|
|
1684
1708
|
/**
|
|
1685
1709
|
* Get the retrievability of the card
|
|
@@ -1724,7 +1748,7 @@
|
|
|
1724
1748
|
const processedCard = TypeConvert.card(card);
|
|
1725
1749
|
const processedLog = TypeConvert.review_log(log);
|
|
1726
1750
|
if (processedLog.rating === Rating.Manual) {
|
|
1727
|
-
throw new
|
|
1751
|
+
throw new FSRSValidationError("Cannot rollback a manual rating");
|
|
1728
1752
|
}
|
|
1729
1753
|
let last_due;
|
|
1730
1754
|
let last_review;
|
|
@@ -1755,11 +1779,7 @@
|
|
|
1755
1779
|
state: processedLog.state,
|
|
1756
1780
|
last_review
|
|
1757
1781
|
});
|
|
1758
|
-
|
|
1759
|
-
return afterHandler(prevCard);
|
|
1760
|
-
} else {
|
|
1761
|
-
return prevCard;
|
|
1762
|
-
}
|
|
1782
|
+
return applyAfterHandler(prevCard, afterHandler);
|
|
1763
1783
|
}
|
|
1764
1784
|
/**
|
|
1765
1785
|
*
|
|
@@ -1841,11 +1861,7 @@
|
|
|
1841
1861
|
last_review: processedCard.last_review
|
|
1842
1862
|
});
|
|
1843
1863
|
const recordLogItem = { card: forget_card, log: forget_log };
|
|
1844
|
-
|
|
1845
|
-
return afterHandler(recordLogItem);
|
|
1846
|
-
} else {
|
|
1847
|
-
return recordLogItem;
|
|
1848
|
-
}
|
|
1864
|
+
return applyAfterHandler(recordLogItem, afterHandler);
|
|
1849
1865
|
}
|
|
1850
1866
|
/**
|
|
1851
1867
|
* Reschedules the current card and returns the rescheduled collections and reschedule item.
|
|
@@ -1912,15 +1928,9 @@
|
|
|
1912
1928
|
len ? collections[len - 1] : void 0,
|
|
1913
1929
|
updateMemoryState
|
|
1914
1930
|
);
|
|
1915
|
-
if (recordLogHandler && typeof recordLogHandler === "function") {
|
|
1916
|
-
return {
|
|
1917
|
-
collections: collections.map(recordLogHandler),
|
|
1918
|
-
reschedule_item: manual_item ? recordLogHandler(manual_item) : null
|
|
1919
|
-
};
|
|
1920
|
-
}
|
|
1921
1931
|
return {
|
|
1922
|
-
collections,
|
|
1923
|
-
reschedule_item: manual_item
|
|
1932
|
+
collections: typeof recordLogHandler === "function" ? collections.map(recordLogHandler) : collections,
|
|
1933
|
+
reschedule_item: manual_item ? applyAfterHandler(manual_item, recordLogHandler) : null
|
|
1924
1934
|
};
|
|
1925
1935
|
}
|
|
1926
1936
|
}
|