ts-fsrs 5.3.3 → 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 +475 -0
- package/dist/index.cjs +57 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +57 -53
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +59 -53
- 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.0";
|
|
487
504
|
|
|
488
505
|
const default_request_retention = 0.9;
|
|
489
506
|
const default_maximum_interval = 36500;
|
|
@@ -572,13 +589,13 @@ const clipParameters = (parameters, numRelearningSteps, enableShortTerm = defaul
|
|
|
572
589
|
);
|
|
573
590
|
};
|
|
574
591
|
const checkParameters = (parameters) => {
|
|
575
|
-
const invalid = parameters.find(
|
|
576
|
-
(param) => !Number.isFinite(param) && !Number.isNaN(param)
|
|
577
|
-
);
|
|
592
|
+
const invalid = parameters.find((param) => !Number.isFinite(param));
|
|
578
593
|
if (invalid !== void 0) {
|
|
579
|
-
throw
|
|
594
|
+
throw new FSRSValidationError(
|
|
595
|
+
`Non-finite or NaN value in parameters ${parameters}`
|
|
596
|
+
);
|
|
580
597
|
} else if (![17, 19, 21].includes(parameters.length)) {
|
|
581
|
-
throw
|
|
598
|
+
throw new FSRSValidationError(
|
|
582
599
|
`Invalid parameter length: ${parameters.length}. Must be 17, 19 or 21 for FSRSv4, 5 and 6 respectively.`
|
|
583
600
|
);
|
|
584
601
|
}
|
|
@@ -696,7 +713,9 @@ class FSRSAlgorithm {
|
|
|
696
713
|
*/
|
|
697
714
|
calculate_interval_modifier(request_retention) {
|
|
698
715
|
if (request_retention <= 0 || request_retention > 1) {
|
|
699
|
-
throw new
|
|
716
|
+
throw new FSRSValidationError(
|
|
717
|
+
"Requested retention rate should be in the range (0,1]"
|
|
718
|
+
);
|
|
700
719
|
}
|
|
701
720
|
const { decay, factor } = computeDecayFactor(this.param.w);
|
|
702
721
|
return roundTo((Math.pow(request_retention, 1 / decay) - 1) / factor, 8);
|
|
@@ -912,10 +931,10 @@ class FSRSAlgorithm {
|
|
|
912
931
|
stability: 0
|
|
913
932
|
};
|
|
914
933
|
if (t < 0) {
|
|
915
|
-
throw new
|
|
934
|
+
throw new FSRSValidationError(`Invalid delta_t "${t}"`);
|
|
916
935
|
}
|
|
917
936
|
if (g < 0 || g > 4) {
|
|
918
|
-
throw new
|
|
937
|
+
throw new FSRSValidationError(`Invalid grade "${g}"`);
|
|
919
938
|
}
|
|
920
939
|
if (d === 0 && s === 0) {
|
|
921
940
|
return {
|
|
@@ -930,7 +949,7 @@ class FSRSAlgorithm {
|
|
|
930
949
|
};
|
|
931
950
|
}
|
|
932
951
|
if (d < 1 || s < S_MIN) {
|
|
933
|
-
throw new
|
|
952
|
+
throw new FSRSValidationError(
|
|
934
953
|
`Invalid memory state { difficulty: ${d}, stability: ${s} }`
|
|
935
954
|
);
|
|
936
955
|
}
|
|
@@ -1311,7 +1330,9 @@ class Reschedule {
|
|
|
1311
1330
|
*/
|
|
1312
1331
|
handleManualRating(card, state, reviewed, elapsed_days, stability, difficulty, due) {
|
|
1313
1332
|
if (typeof state === "undefined") {
|
|
1314
|
-
throw new
|
|
1333
|
+
throw new FSRSValidationError(
|
|
1334
|
+
"reschedule: state is required for manual rating"
|
|
1335
|
+
);
|
|
1315
1336
|
}
|
|
1316
1337
|
let log;
|
|
1317
1338
|
let next_card;
|
|
@@ -1332,7 +1353,9 @@ class Reschedule {
|
|
|
1332
1353
|
next_card.last_review = reviewed;
|
|
1333
1354
|
} else {
|
|
1334
1355
|
if (typeof due === "undefined") {
|
|
1335
|
-
throw new
|
|
1356
|
+
throw new FSRSValidationError(
|
|
1357
|
+
"reschedule: due is required for manual rating"
|
|
1358
|
+
);
|
|
1336
1359
|
}
|
|
1337
1360
|
const scheduled_days = date_diff(due, reviewed, "days");
|
|
1338
1361
|
log = {
|
|
@@ -1422,6 +1445,9 @@ class Reschedule {
|
|
|
1422
1445
|
}
|
|
1423
1446
|
}
|
|
1424
1447
|
|
|
1448
|
+
function applyAfterHandler(value, afterHandler) {
|
|
1449
|
+
return typeof afterHandler === "function" ? afterHandler(value) : value;
|
|
1450
|
+
}
|
|
1425
1451
|
class FSRS extends FSRSAlgorithm {
|
|
1426
1452
|
strategyHandler = /* @__PURE__ */ new Map();
|
|
1427
1453
|
Scheduler;
|
|
@@ -1537,11 +1563,7 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1537
1563
|
repeat(card, now, afterHandler) {
|
|
1538
1564
|
const instance = this.getScheduler(card, now);
|
|
1539
1565
|
const recordLog = instance.preview();
|
|
1540
|
-
|
|
1541
|
-
return afterHandler(recordLog);
|
|
1542
|
-
} else {
|
|
1543
|
-
return recordLog;
|
|
1544
|
-
}
|
|
1566
|
+
return applyAfterHandler(recordLog, afterHandler);
|
|
1545
1567
|
}
|
|
1546
1568
|
/**
|
|
1547
1569
|
* Display the collection of cards and logs for the card scheduled at the current time, after applying a specific grade rating.
|
|
@@ -1601,14 +1623,10 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1601
1623
|
const instance = this.getScheduler(card, now);
|
|
1602
1624
|
const g = TypeConvert.rating(grade);
|
|
1603
1625
|
if (g === Rating.Manual) {
|
|
1604
|
-
throw new
|
|
1626
|
+
throw new FSRSValidationError("Cannot review a manual rating");
|
|
1605
1627
|
}
|
|
1606
1628
|
const recordLogItem = instance.review(g);
|
|
1607
|
-
|
|
1608
|
-
return afterHandler(recordLogItem);
|
|
1609
|
-
} else {
|
|
1610
|
-
return recordLogItem;
|
|
1611
|
-
}
|
|
1629
|
+
return applyAfterHandler(recordLogItem, afterHandler);
|
|
1612
1630
|
}
|
|
1613
1631
|
/**
|
|
1614
1632
|
* Get the retrievability of the card
|
|
@@ -1653,7 +1671,7 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1653
1671
|
const processedCard = TypeConvert.card(card);
|
|
1654
1672
|
const processedLog = TypeConvert.review_log(log);
|
|
1655
1673
|
if (processedLog.rating === Rating.Manual) {
|
|
1656
|
-
throw new
|
|
1674
|
+
throw new FSRSValidationError("Cannot rollback a manual rating");
|
|
1657
1675
|
}
|
|
1658
1676
|
let last_due;
|
|
1659
1677
|
let last_review;
|
|
@@ -1685,11 +1703,7 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1685
1703
|
state: processedLog.state,
|
|
1686
1704
|
last_review
|
|
1687
1705
|
};
|
|
1688
|
-
|
|
1689
|
-
return afterHandler(prevCard);
|
|
1690
|
-
} else {
|
|
1691
|
-
return prevCard;
|
|
1692
|
-
}
|
|
1706
|
+
return applyAfterHandler(prevCard, afterHandler);
|
|
1693
1707
|
}
|
|
1694
1708
|
/**
|
|
1695
1709
|
*
|
|
@@ -1772,11 +1786,7 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1772
1786
|
last_review: processedCard.last_review
|
|
1773
1787
|
};
|
|
1774
1788
|
const recordLogItem = { card: forget_card, log: forget_log };
|
|
1775
|
-
|
|
1776
|
-
return afterHandler(recordLogItem);
|
|
1777
|
-
} else {
|
|
1778
|
-
return recordLogItem;
|
|
1779
|
-
}
|
|
1789
|
+
return applyAfterHandler(recordLogItem, afterHandler);
|
|
1780
1790
|
}
|
|
1781
1791
|
/**
|
|
1782
1792
|
* Reschedules the current card and returns the rescheduled collections and reschedule item.
|
|
@@ -1843,15 +1853,9 @@ class FSRS extends FSRSAlgorithm {
|
|
|
1843
1853
|
len ? collections[len - 1] : void 0,
|
|
1844
1854
|
updateMemoryState
|
|
1845
1855
|
);
|
|
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
1856
|
return {
|
|
1853
|
-
collections,
|
|
1854
|
-
reschedule_item: manual_item
|
|
1857
|
+
collections: typeof recordLogHandler === "function" ? collections.map(recordLogHandler) : collections,
|
|
1858
|
+
reschedule_item: manual_item ? applyAfterHandler(manual_item, recordLogHandler) : null
|
|
1855
1859
|
};
|
|
1856
1860
|
}
|
|
1857
1861
|
}
|