aac51bf183
Implement schema v2 migration, outline/setting/inspiration CRUD, unified TipTap editing, reference panel with mentions, FTS global search, chapter version history, writing landmarks, word frequency analysis, light theme contrast fixes, and full unit/E2E test coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
82 lines
2.3 KiB
TypeScript
82 lines
2.3 KiB
TypeScript
import { randomUUID } from 'crypto'
|
|
import type { Snapshot, SnapshotType } from '../../../shared/types'
|
|
import type { SqliteDb } from '../types'
|
|
|
|
function mapRow(row: Record<string, unknown>): Snapshot {
|
|
return {
|
|
id: row.id as string,
|
|
chapterId: row.chapter_id as string,
|
|
type: row.type as SnapshotType,
|
|
name: (row.name as string) ?? '',
|
|
content: row.content as string,
|
|
createdAt: row.created_at as string
|
|
}
|
|
}
|
|
|
|
export class SnapshotRepository {
|
|
constructor(private db: SqliteDb) {}
|
|
|
|
create(chapterId: string, type: SnapshotType, content: string, name = ''): Snapshot {
|
|
const id = randomUUID()
|
|
this.db
|
|
.prepare(`INSERT INTO snapshots (id, chapter_id, type, name, content) VALUES (?, ?, ?, ?, ?)`)
|
|
.run(id, chapterId, type, name, content)
|
|
return this.get(id)!
|
|
}
|
|
|
|
get(id: string): Snapshot | null {
|
|
const row = this.db.prepare('SELECT * FROM snapshots WHERE id = ?').get(id) as
|
|
| Record<string, unknown>
|
|
| undefined
|
|
return row ? mapRow(row) : null
|
|
}
|
|
|
|
list(chapterId: string): Snapshot[] {
|
|
return (
|
|
this.db
|
|
.prepare('SELECT * FROM snapshots WHERE chapter_id = ? ORDER BY created_at DESC')
|
|
.all(chapterId) as Record<string, unknown>[]
|
|
).map(mapRow)
|
|
}
|
|
|
|
delete(id: string): void {
|
|
this.db.prepare('DELETE FROM snapshots WHERE id = ?').run(id)
|
|
}
|
|
|
|
pruneAuto(chapterId: string, max: number): void {
|
|
const autos = (
|
|
this.db
|
|
.prepare(
|
|
`SELECT id FROM snapshots WHERE chapter_id = ? AND type = 'auto' ORDER BY created_at ASC`
|
|
)
|
|
.all(chapterId) as { id: string }[]
|
|
)
|
|
const excess = autos.length - max
|
|
if (excess <= 0) return
|
|
for (let i = 0; i < excess; i++) {
|
|
this.delete(autos[i].id)
|
|
}
|
|
}
|
|
|
|
prunePersist(chapterId: string, max: number): void {
|
|
const items = (
|
|
this.db
|
|
.prepare(
|
|
`SELECT id FROM snapshots WHERE chapter_id = ? AND type = 'persist' ORDER BY created_at ASC`
|
|
)
|
|
.all(chapterId) as { id: string }[]
|
|
)
|
|
const excess = items.length - max
|
|
if (excess <= 0) return
|
|
for (let i = 0; i < excess; i++) {
|
|
this.delete(items[i].id)
|
|
}
|
|
}
|
|
|
|
restore(snapshotId: string): string {
|
|
const snap = this.get(snapshotId)
|
|
if (!snap) throw new Error('Snapshot not found')
|
|
return snap.content
|
|
}
|
|
}
|