ts-fsrs 5.3.3 → 5.4.1
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 +62 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +62 -54
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +64 -54
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
class FSRSError extends Error {
|
|
4
|
+
constructor(message = "FSRS Error") {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "FSRSError";
|
|
7
|
+
Error.captureStackTrace?.(this, FSRSError);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
class FSRSValidationError extends FSRSError {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "FSRSValidationError";
|
|
14
|
+
Error.captureStackTrace?.(this, FSRSValidationError);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
3
18
|
var State = /* @__PURE__ */ ((State2) => {
|
|
4
19
|
State2[State2["New"] = 0] = "New";
|
|
5
20
|
State2[State2["Learning"] = 1] = "Learning";
|
|
@@ -31,13 +46,13 @@ class TypeConvert {
|
|
|
31
46
|
const restOfString = value.slice(1).toLowerCase();
|
|
32
47
|
const ret = Rating[`${firstLetter}${restOfString}`];
|
|
33
48
|
if (ret === void 0) {
|
|
34
|
-
throw new
|
|
49
|
+
throw new FSRSValidationError(`Invalid rating:[${value}]`);
|
|
35
50
|
}
|
|
36
51
|
return ret;
|
|
37
52
|
} else if (typeof value === "number") {
|
|
38
53
|
return value;
|
|
39
54
|
}
|
|
40
|
-
throw new
|
|
55
|
+
throw new FSRSValidationError(`Invalid rating:[${value}]`);
|
|
41
56
|
}
|
|
42
57
|
static state(value) {
|
|
43
58
|
if (typeof value === "string") {
|
|
@@ -45,13 +60,13 @@ class TypeConvert {
|
|
|
45
60
|
const restOfString = value.slice(1).toLowerCase();
|
|
46
61
|
const ret = State[`${firstLetter}${restOfString}`];
|
|
47
62
|
if (ret === void 0) {
|
|
48
|
-
throw new
|
|
63
|
+
throw new FSRSValidationError(`Invalid state:[${value}]`);
|
|
49
64
|
}
|
|
50
65
|
return ret;
|
|
51
66
|
} else if (typeof value === "number") {
|
|
52
67
|
return value;
|
|
53
68
|
}
|
|
54
|
-
throw new
|
|
69
|
+
throw new FSRSValidationError(`Invalid state:[${value}]`);
|
|
55
70
|
}
|
|
56
71
|
static time(value) {
|
|
57
72
|
if (value instanceof Date) {
|
|
@@ -65,12 +80,12 @@ class TypeConvert {
|
|
|
65
80
|
if (!Number.isNaN(timestamp)) {
|
|
66
81
|
return new Date(timestamp);
|
|
67
82
|
} else {
|
|
68
|
-
throw new
|
|
83
|
+
throw new FSRSValidationError(`Invalid date:[${value}]`);
|
|
69
84
|
}
|
|
70
85
|
} else if (typeof value === "number") {
|
|
71
86
|
return new Date(value);
|
|
72
87
|
}
|
|
73
|
-
throw new
|
|
88
|
+
throw new FSRSValidationError(`Invalid date:[${value}]`);
|
|
74
89
|
}
|
|
75
90
|
static review_log(log) {
|
|
76
91
|
return {
|
|
@@ -106,7 +121,7 @@ function date_scheduler(now, t, isDay) {
|
|
|
106
121
|
}
|
|
107
122
|
function date_diff(now, pre, unit) {
|
|
108
123
|
if (!now || !pre) {
|
|
109
|
-
throw new
|
|
124
|
+
throw new FSRSValidationError("Invalid date");
|
|
110
125
|
}
|
|
111
126
|
const diff = TypeConvert.time(now).getTime() - TypeConvert.time(pre).getTime();
|
|
112
127
|
let r = 0;
|
|
@@ -232,7 +247,7 @@ const ConvertStepUnitToMinutes = (step) => {
|
|
|
232
247
|
const unit = step.slice(-1);
|
|
233
248
|
const value = parseInt(step.slice(0, -1), 10);
|
|
234
249
|
if (Number.isNaN(value) || !Number.isFinite(value) || value < 0) {
|
|
235
|
-
throw new
|
|
250
|
+
throw new FSRSValidationError(`Invalid step value: ${step}`);
|
|
236
251
|
}
|
|
237
252
|
switch (unit) {
|
|
238
253
|
case "m":
|
|
@@ -242,7 +257,9 @@ const ConvertStepUnitToMinutes = (step) => {
|
|
|
242
257
|
case "d":
|
|
243
258
|
return value * 1440;
|
|
244
259
|
default:
|
|
245
|
-
throw new
|
|
260
|
+
throw new FSRSValidationError(
|
|
261
|
+
`Invalid step unit: ${step}, expected m/h/d`
|
|
262
|
+
);
|
|
246
263
|
}
|
|
247
264
|
};
|
|
248
265
|
const BasicLearningStepsStrategy = (params, state, cur_step) => {
|
|
@@ -339,8 +356,8 @@ class AbstractScheduler {
|
|
|
339
356
|
this.init();
|
|
340
357
|
}
|
|
341
358
|
checkGrade(grade) {
|
|
342
|
-
if (!Number.isFinite(grade) || grade <
|
|
343
|
-
throw new
|
|
359
|
+
if (!Number.isFinite(grade) || grade < 1 || grade > 4) {
|
|
360
|
+
throw new FSRSValidationError(`Invalid grade "${grade}",expected 1-4`);
|
|
344
361
|
}
|
|
345
362
|
}
|
|
346
363
|
init() {
|
|
@@ -483,7 +500,7 @@ function alea(seed) {
|
|
|
483
500
|
return prng;
|
|
484
501
|
}
|
|
485
502
|
|
|
486
|
-
const version="5.
|
|
503
|
+
const version="5.4.1";
|
|
487
504
|
|
|
488
505
|
const default_request_retention = 0.9;
|
|
489
506
|
const default_maximum_interval = 36500;
|
|
@@ -563,7 +580,11 @@ const clipParameters = (parameters, numRelearningSteps, enableShortTerm = defaul
|
|
|
563
580
|
const w13 = clamp(parameters[13] || 0, clip[13][0], clip[13][1]);
|
|
564
581
|
const w14 = clamp(parameters[14] || 0, clip[14][0], clip[14][1]);
|
|
565
582
|
const value = -(Math.log(w11) + Math.log(Math.pow(2, w13) - 1) + w14 * 0.3) / numRelearningSteps;
|
|
566
|
-
const w17_w18_ceiling = clamp(
|
|
583
|
+
const w17_w18_ceiling = clamp(
|
|
584
|
+
roundTo(Math.sqrt(Math.max(value, 0)), 8),
|
|
585
|
+
0.01,
|
|
586
|
+
W17_W18_Ceiling
|
|
587
|
+
);
|
|
567
588
|
if (clip[17]) clip[17] = [clip[17][0], w17_w18_ceiling];
|
|
568
589
|
if (clip[18]) clip[18] = [clip[18][0], w17_w18_ceiling];
|
|
569
590
|
}
|
|
@@ -572,13 +593,13 @@ const clipParameters = (parameters, numRelearningSteps, enableShortTerm = defaul
|
|
|
572
593
|
);
|
|
573
594
|
};
|
|
574
595
|
const checkParameters = (parameters) => {
|
|
575
|
-
const invalid = parameters.find(
|
|
576
|
-
(param) => !Number.isFinite(param) && !Number.isNaN(param)
|
|
577
|
-
);
|
|
596
|
+
const invalid = parameters.find((param) => !Number.isFinite(param));
|
|
578
597
|
if (invalid !== void 0) {
|
|
579
|
-
throw
|
|
598
|
+
throw new FSRSValidationError(
|
|
599
|
+
`Non-finite or NaN value in parameters ${parameters}`
|
|
600
|
+
);
|
|
580
601
|
} else if (![17, 19, 21].includes(parameters.length)) {
|
|
581
|
-
throw
|
|
602
|
+
throw new FSRSValidationError(
|
|
582
603
|
`Invalid parameter length: ${parameters.length}. Must be 17, 19 or 21 for FSRSv4, 5 and 6 respectively.`
|
|
583
604
|
);
|
|
584
605
|
}
|
|
@@ -696,7 +717,9 @@ class FSRSAlgorithm {
|
|
|
696
717
|
*/
|
|
697
718
|
calculate_interval_modifier(request_retention) {
|
|
698
719
|
if (request_retention <= 0 || request_retention > 1) {
|
|
699
|
-
throw new
|
|
720
|
+
throw new FSRSValidationError(
|
|
721
|
+
"Requested retention rate should be in the range (0,1]"
|
|
722
|
+
);
|
|
700
723
|
}
|
|
701
724
|
const { decay, factor } = computeDecayFactor(this.param.w);
|
|
702
725
|
return roundTo((Math.pow(request_retention, 1 / decay) - 1) / factor, 8);
|
|
@@ -912,10 +935,10 @@ class FSRSAlgorithm {
|
|
|
912
935
|
stability: 0
|
|
913
936
|
};
|
|
914
937
|
if (t < 0) {
|
|
915
|
-
throw new
|
|
938
|
+
throw new FSRSValidationError(`Invalid delta_t "${t}"`);
|
|
916
939
|
}
|
|
917
940
|
if (g < 0 || g > 4) {
|
|
918
|
-
throw new
|
|
941
|
+
throw new FSRSValidationError(`Invalid grade "${g}"`);
|
|
919
942
|
}
|
|
920
943
|
if (d === 0 && s === 0) {
|
|
921
944
|
return {
|
|
@@ -930,7 +953,7 @@ class FSRSAlgorithm {
|
|
|
930
953
|
};
|
|
931
954
|
}
|
|
932
955
|
if (d < 1 || s < S_MIN) {
|
|
933
|
-
throw new
|
|
956
|
+
throw new FSRSValidationError(
|
|
934
957
|
`Invalid memory state { difficulty: ${d}, stability: ${s} }`
|
|
935
958
|
);
|
|
936
959
|
}
|
|
@@ -1311,7 +1334,9 @@ class Reschedule {
|
|
|
1311
1334
|
*/
|
|
1312
1335
|
handleManualRating(card, state, reviewed, elapsed_days, stability, difficulty, due) {
|
|
1313
1336
|
if (typeof state === "undefined") {
|
|
1314
|
-
throw new
|
|
1337
|
+
throw new FSRSValidationError(
|
|
1338
|
+
"reschedule: state is required for manual rating"
|
|
1339
|
+
);
|
|
1315
1340
|
}
|
|
1316
1341
|
let log;
|
|
1317
1342
|
let next_card;
|
|
@@ -1332,7 +1357,9 @@ class Reschedule {
|
|
|
1332
1357
|
next_card.last_review = reviewed;
|
|
1333
1358
|
} else {
|
|
1334
1359
|
if (typeof due === "undefined") {
|
|
1335
|
-
throw new
|
|
1360
|
+
throw new FSRSValidationError(
|
|
1361
|
+
"reschedule: due is required for manual rating"
|
|
1362
|
+
);
|
|
1336
1363
|
}
|
|
1337
1364
|
const scheduled_days = date_diff(due, reviewed, "days");
|
|
1338
1365
|
log = {
|
|
@@ -1422,6 +1449,9 @@ class Reschedule {
|
|
|
1422
1449
|
}
|
|
1423
1450
|
}
|
|
1424
1451
|
|
|
1452
|
+
function applyAfterHandler(value, afterHandler) {
|
|
1453
|
+
return typeof afterHandler === "function" ? afterHandler(value) : value;
|
|
1454
|
+
}
|
|
1425
1455
|
class FSRS extends FSRSAlgorithm {
|
|
1426
1456
|
strategyHandler = /* @__PURE__ */ new Map();
|
|
1427
1457
|
Scheduler;
|
|
@@ -1537,11 +1567,7 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1537
1567
|
repeat(card, now, afterHandler) {
|
|
1538
1568
|
const instance = this.getScheduler(card, now);
|
|
1539
1569
|
const recordLog = instance.preview();
|
|
1540
|
-
|
|
1541
|
-
return afterHandler(recordLog);
|
|
1542
|
-
} else {
|
|
1543
|
-
return recordLog;
|
|
1544
|
-
}
|
|
1570
|
+
return applyAfterHandler(recordLog, afterHandler);
|
|
1545
1571
|
}
|
|
1546
1572
|
/**
|
|
1547
1573
|
* Display the collection of cards and logs for the card scheduled at the current time, after applying a specific grade rating.
|
|
@@ -1601,14 +1627,10 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1601
1627
|
const instance = this.getScheduler(card, now);
|
|
1602
1628
|
const g = TypeConvert.rating(grade);
|
|
1603
1629
|
if (g === Rating.Manual) {
|
|
1604
|
-
throw new
|
|
1630
|
+
throw new FSRSValidationError("Cannot review a manual rating");
|
|
1605
1631
|
}
|
|
1606
1632
|
const recordLogItem = instance.review(g);
|
|
1607
|
-
|
|
1608
|
-
return afterHandler(recordLogItem);
|
|
1609
|
-
} else {
|
|
1610
|
-
return recordLogItem;
|
|
1611
|
-
}
|
|
1633
|
+
return applyAfterHandler(recordLogItem, afterHandler);
|
|
1612
1634
|
}
|
|
1613
1635
|
/**
|
|
1614
1636
|
* Get the retrievability of the card
|
|
@@ -1653,7 +1675,7 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1653
1675
|
const processedCard = TypeConvert.card(card);
|
|
1654
1676
|
const processedLog = TypeConvert.review_log(log);
|
|
1655
1677
|
if (processedLog.rating === Rating.Manual) {
|
|
1656
|
-
throw new
|
|
1678
|
+
throw new FSRSValidationError("Cannot rollback a manual rating");
|
|
1657
1679
|
}
|
|
1658
1680
|
let last_due;
|
|
1659
1681
|
let last_review;
|
|
@@ -1685,11 +1707,7 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1685
1707
|
state: processedLog.state,
|
|
1686
1708
|
last_review
|
|
1687
1709
|
};
|
|
1688
|
-
|
|
1689
|
-
return afterHandler(prevCard);
|
|
1690
|
-
} else {
|
|
1691
|
-
return prevCard;
|
|
1692
|
-
}
|
|
1710
|
+
return applyAfterHandler(prevCard, afterHandler);
|
|
1693
1711
|
}
|
|
1694
1712
|
/**
|
|
1695
1713
|
*
|
|
@@ -1772,11 +1790,7 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1772
1790
|
last_review: processedCard.last_review
|
|
1773
1791
|
};
|
|
1774
1792
|
const recordLogItem = { card: forget_card, log: forget_log };
|
|
1775
|
-
|
|
1776
|
-
return afterHandler(recordLogItem);
|
|
1777
|
-
} else {
|
|
1778
|
-
return recordLogItem;
|
|
1779
|
-
}
|
|
1793
|
+
return applyAfterHandler(recordLogItem, afterHandler);
|
|
1780
1794
|
}
|
|
1781
1795
|
/**
|
|
1782
1796
|
* Reschedules the current card and returns the rescheduled collections and reschedule item.
|
|
@@ -1843,15 +1857,9 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1843
1857
|
len ? collections[len - 1] : void 0,
|
|
1844
1858
|
updateMemoryState
|
|
1845
1859
|
);
|
|
1846
|
-
if (recordLogHandler && typeof recordLogHandler === "function") {
|
|
1847
|
-
return {
|
|
1848
|
-
collections: collections.map(recordLogHandler),
|
|
1849
|
-
reschedule_item: manual_item ? recordLogHandler(manual_item) : null
|
|
1850
|
-
};
|
|
1851
|
-
}
|
|
1852
1860
|
return {
|
|
1853
|
-
collections,
|
|
1854
|
-
reschedule_item: manual_item
|
|
1861
|
+
collections: typeof recordLogHandler === "function" ? collections.map(recordLogHandler) : collections,
|
|
1862
|
+
reschedule_item: manual_item ? applyAfterHandler(manual_item, recordLogHandler) : null
|
|
1855
1863
|
};
|
|
1856
1864
|
}
|
|
1857
1865
|
}
|