Phase 3 follow-up: type-check + tests grün, ts-fsrs v5 API

- tsconfig.base.json: allowImportingTsExtensions + noEmit (.ts-Imports
  in dev, kein tsc-Output, vitest/bun/vite handhaben Build)
- ts-fsrs v5.3.2 API-Updates:
  - scheduler.next(card, now, grade) statt repeat(card, now)[rating].card
  - Grade-Type für RATING_TO_FSRS (excluded Manual)
  - learning_steps-Feld auf Review (Schema, Drizzle-Column, Adapter,
    DTO-Konverter, Tests)
- apps/web: extends .svelte-kit/tsconfig.json (SvelteKit-Empfehlung),
  test-Script mit --passWithNoTests
- apps/api: dropped types: ['bun-types'] (stale)
- pnpm-lock.yaml committed

Status:
- pnpm run type-check  4/4 packages grün (api, domain, web mit
  svelte-check 0 errors)
- pnpm run test  46 Tests grün (cards-domain: 27, apps/api: 19,
  apps/web: --passWithNoTests)
- pnpm install  136 packages, 8s

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till 2026-05-08 14:41:04 +02:00
parent 45a47e0ffd
commit 5f67bd9f3e
11 changed files with 2345 additions and 35 deletions

View file

@ -8,20 +8,18 @@
import {
createEmptyCard,
default_w,
FSRS,
generatorParameters,
Rating as FsrsRating,
State as FsrsState,
type Card as FsrsCard,
type FSRSParameters,
type Grade,
} from 'ts-fsrs';
import type { Rating, Review, ReviewState } from './schemas/review.ts';
import type { FsrsSettings } from './schemas/fsrs-settings.ts';
/** Public Rating ↔ ts-fsrs Rating mapping. */
const RATING_TO_FSRS: Record<Rating, FsrsRating> = {
/** Public Rating ↔ ts-fsrs Grade (Manual ist explizit ausgeschlossen). */
const RATING_TO_FSRS: Record<Rating, Grade> = {
again: FsrsRating.Again,
hard: FsrsRating.Hard,
good: FsrsRating.Good,
@ -45,13 +43,14 @@ const STATE_TO_FSRS: Record<ReviewState, FsrsState> = {
/** Baut einen FSRS-Scheduler aus per-Deck-Settings + globalen Defaults. */
export function buildScheduler(settings: FsrsSettings = {}): FSRS {
const params: FSRSParameters = generatorParameters({
request_retention: settings.request_retention,
maximum_interval: settings.maximum_interval,
w: settings.w ?? default_w,
enable_fuzz: settings.enable_fuzz ?? true,
return new FSRS({
...(settings.request_retention !== undefined && {
request_retention: settings.request_retention,
}),
...(settings.maximum_interval !== undefined && { maximum_interval: settings.maximum_interval }),
...(settings.w !== undefined && { w: settings.w }),
...(settings.enable_fuzz !== undefined && { enable_fuzz: settings.enable_fuzz }),
});
return new FSRS(params);
}
/** Initialer Review-State für eine neue Karte (sub_index). */
@ -72,6 +71,7 @@ export function newReview(args: {
difficulty: fc.difficulty,
elapsed_days: fc.elapsed_days,
scheduled_days: fc.scheduled_days,
learning_steps: fc.learning_steps,
reps: fc.reps,
lapses: fc.lapses,
state: STATE_FROM_FSRS[fc.state],
@ -88,10 +88,8 @@ export function gradeReview(
): Review {
const reviewedAt = now ?? new Date();
const scheduler = buildScheduler(settings);
const fc = toFsrsCard(current);
const log = scheduler.repeat(fc, reviewedAt);
const next = log[RATING_TO_FSRS[rating]].card;
return fromFsrsCard(current, next);
const result = scheduler.next(toFsrsCard(current), reviewedAt, RATING_TO_FSRS[rating]);
return fromFsrsCard(current, result.card);
}
/** Konvertiert unseren Review-Datensatz in eine ts-fsrs Card. */
@ -102,6 +100,7 @@ export function toFsrsCard(r: Review): FsrsCard {
difficulty: r.difficulty,
elapsed_days: r.elapsed_days,
scheduled_days: r.scheduled_days,
learning_steps: r.learning_steps,
reps: r.reps,
lapses: r.lapses,
state: STATE_TO_FSRS[r.state],
@ -118,6 +117,7 @@ export function fromFsrsCard(prev: Review, fc: FsrsCard): Review {
difficulty: fc.difficulty,
elapsed_days: fc.elapsed_days,
scheduled_days: fc.scheduled_days,
learning_steps: fc.learning_steps,
reps: fc.reps,
lapses: fc.lapses,
state: STATE_FROM_FSRS[fc.state],

View file

@ -17,6 +17,7 @@ export const ReviewSchema = z
difficulty: z.number().min(0).max(10),
elapsed_days: z.number().nonnegative().default(0),
scheduled_days: z.number().nonnegative().default(0),
learning_steps: z.number().int().nonnegative().default(0),
reps: z.number().int().nonnegative().default(0),
lapses: z.number().int().nonnegative().default(0),
state: ReviewStateSchema.default('new'),

View file

@ -2,7 +2,6 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true