blob: 115bbcd2362d620d10e8ee862d195203e183e65c (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
import { atomFamily } from "jotai-family";
import { atomWithSuspenseQuery } from "jotai-tanstack-query";
import { getStartOfStudyDayBoundary } from "../../shared/date";
import { localDeckRepository } from "../db/repositories";
import { buildStudyCards, type StudyCardView } from "../db/study-builder";
import { createSeededRandom, shuffle } from "../utils/random";
import { ensureBootstrap } from "./sync";
export type StudyCard = StudyCardView;
export interface StudyDeck {
id: string;
name: string;
}
export interface StudyData {
deck: StudyDeck;
cards: StudyCard[];
}
async function loadStudyData(deckId: string): Promise<StudyData | null> {
const deck = await localDeckRepository.findById(deckId);
if (!deck || deck.deletedAt !== null) return null;
const cards = await buildStudyCards(deckId);
const seed = getStartOfStudyDayBoundary().getTime();
return {
deck: { id: deck.id, name: deck.name },
cards: shuffle(cards, createSeededRandom(seed)),
};
}
// =====================
// Study Session - Suspense-compatible, IndexedDB-first
// =====================
export const studyDataAtomFamily = atomFamily((deckId: string) =>
atomWithSuspenseQuery(() => ({
queryKey: ["decks", deckId, "study"],
queryFn: async (): Promise<StudyData> => {
let data = await loadStudyData(deckId);
if (data) {
ensureBootstrap();
return data;
}
await ensureBootstrap();
data = await loadStudyData(deckId);
if (!data) {
throw new Error(`Deck not found: ${deckId}`);
}
return data;
},
})),
);
|