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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
import Dexie, { type EntityTable } from "dexie";
/**
* Card states for FSRS algorithm
*/
export const CardState = {
New: 0,
Learning: 1,
Review: 2,
Relearning: 3,
} as const;
export type CardStateType = (typeof CardState)[keyof typeof CardState];
/**
* Rating values for reviews
*/
export const Rating = {
Again: 1,
Hard: 2,
Good: 3,
Easy: 4,
} as const;
export type RatingType = (typeof Rating)[keyof typeof Rating];
/**
* Local deck stored in IndexedDB
* Includes _synced flag for offline sync tracking
*/
export interface LocalDeck {
id: string;
userId: string;
name: string;
description: string | null;
newCardsPerDay: number;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
syncVersion: number;
_synced: boolean;
}
/**
* Local card stored in IndexedDB
* Includes _synced flag for offline sync tracking
*/
export interface LocalCard {
id: string;
deckId: string;
front: string;
back: string;
// FSRS fields
state: CardStateType;
due: Date;
stability: number;
difficulty: number;
elapsedDays: number;
scheduledDays: number;
reps: number;
lapses: number;
lastReview: Date | null;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
syncVersion: number;
_synced: boolean;
}
/**
* Local review log stored in IndexedDB
* Includes _synced flag for offline sync tracking
* ReviewLog is append-only (no conflicts possible)
*/
export interface LocalReviewLog {
id: string;
cardId: string;
userId: string;
rating: RatingType;
state: CardStateType;
scheduledDays: number;
elapsedDays: number;
reviewedAt: Date;
durationMs: number | null;
syncVersion: number;
_synced: boolean;
}
/**
* Kioku local database using Dexie (IndexedDB wrapper)
*
* This database stores decks, cards, and review logs locally for offline support.
* Each entity has a _synced flag to track whether it has been synchronized with the server.
*/
export class KiokuDatabase extends Dexie {
decks!: EntityTable<LocalDeck, "id">;
cards!: EntityTable<LocalCard, "id">;
reviewLogs!: EntityTable<LocalReviewLog, "id">;
constructor() {
super("kioku");
this.version(1).stores({
// Primary key is 'id', indexed fields follow
// userId: for filtering by user
// updatedAt: for sync ordering
// Note: _synced is not indexed (boolean fields can't be indexed in IndexedDB)
// Use .filter() to find unsynced items
decks: "id, userId, updatedAt",
// deckId: for filtering cards by deck
// due: for finding due cards
// state: for filtering by card state
cards: "id, deckId, updatedAt, due, state",
// cardId: for finding reviews for a card
// userId: for filtering by user
// reviewedAt: for ordering reviews
reviewLogs: "id, cardId, userId, reviewedAt",
});
}
}
/**
* Singleton instance of the Kioku database
*/
export const db = new KiokuDatabase();
|