-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathresponses.ts
More file actions
157 lines (141 loc) · 5.33 KB
/
Copy pathresponses.ts
File metadata and controls
157 lines (141 loc) · 5.33 KB
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import type { z } from 'zod'
import type { BookMeta, LearningProfile } from './domain.js'
import type { ImportEpubPreviewResponseSchema } from './contracts.js'
/**
* The HTTP response bodies the server actually sends today, for the routes
* whose shapes the client was re-declaring locally and drifting from.
*
* Every type here is read off the route handler that produces it, not
* designed from scratch, so it reflects the real shape on the wire rather
* than an idealized one. Prefer importing from here over re-declaring a
* response shape in client/.
*
* Request bodies live in shared/contracts.ts. SSE event unions live in
* shared/events.ts. This file is types only — it compiles away entirely.
*/
/** GET /api/books — one library card: book meta augmented with cover, progress, and audiobook flags. */
export type LibraryBook = BookMeta & {
hasCover: boolean
showTitleOnCover: boolean
coverUpdatedAt: string | null
chaptersRead: number
hasAudiobook: boolean
}
/** The stage a background chapter generation is in, consumed directly by server/services/chapter-generation-stream.ts, not mirrored from elsewhere. */
export type GenerationStage = 'streaming' | 'saving' | 'quiz' | 'done' | 'error'
/** Mirrors TextGenerationErrorKind in server/ports/text-generation.ts, so the client can switch on an AI failure class without importing zod or anything under server/. Pinned together by the drift guard in server/ports/ai-error-kind.drift.test.ts. */
export type AiErrorKind =
| 'auth-failed'
| 'rate-limited'
| 'overloaded'
| 'timed-out'
| 'network-failed'
| 'content-refused'
| 'unknown'
/**
* GET /api/books/:id/generation-status — background chapter generation
* progress for one book. `error` is set on the active variant when a
* generation ended in error, including one that was interrupted by a
* server restart and seeded that way at boot rather than having actually
* streamed and failed live. The reader surfaces it through its existing
* generation-error panel either way.
*/
export type GenerationStatus =
| { active: false }
| { active: true; chapterNum: number; stage: GenerationStage; contentLength: number; error?: string; errorKind?: AiErrorKind }
/** GET /api/books/:id — book meta plus the current generation status. */
export type BookDetail = BookMeta & { generation: GenerationStatus }
/** GET /api/books/search — title, TOC, and chapter matches for a query. */
export type SearchResults = {
results: Array<{
bookId: string
matches: Array<{
type: 'title' | 'toc' | 'chapter'
chapter?: number
snippet: string
}>
}>
}
/** GET /api/progress/skills — skill mastery rolled up across every book. */
export type SkillProgress = {
stats: {
totalBooks: number
completedBooks: number
totalChapters: number
completedChapters: number
}
skills: Array<{
name: string
totalWeight: number
completedWeight: number
lastActivityAt?: string
books: Array<{
bookId: string
title: string
weight: number
completed: boolean
lastActivityAt?: string
}>
subskills: Array<{ name: string; totalWeight: number; completedWeight: number }>
}>
}
/** The kind of background job a task runs, consumed directly by server/ports/background-tasks.ts's StartTaskSpec, not mirrored from elsewhere. */
export type TaskType =
| 'generate-all'
| 'generate-epub'
| 'generate-cover'
| 'install-audiobook'
| 'generate-audiobook'
/** A background task's lifecycle state, consumed directly by server/ports/background-tasks.ts's Task alias, not mirrored from elsewhere. */
export type TaskStatus = 'running' | 'done' | 'error' | 'cancelled'
/** A background task's current progress, consumed directly by server/ports/background-tasks.ts's Task alias, not mirrored from elsewhere. */
export type TaskProgress = {
current: number
total: number
label: string
}
/** GET /api/tasks — one background task, as sent to the client. */
export type ClientTask = {
id: string
type: TaskType
bookId: string
bookTitle: string
status: TaskStatus
progress: TaskProgress
error?: string
result?: unknown
}
/**
* POST /api/books/import/preview — parsed EPUB metadata shown before import
* is confirmed. Same shape as ImportEpubPreviewResponseSchema in
* shared/contracts.ts, inferred rather than restated.
*/
export type EpubPreview = z.infer<typeof ImportEpubPreviewResponseSchema>
/** An element of GET /api/audiobook/voices. */
export type VoiceInfo = {
id: string
name: string
language: 'American English' | 'British English'
gender: 'Male' | 'Female'
grade: string
}
/**
* GET /api/profile — the learning profile as the client sees it.
*
* Deliberately NOT `LearningProfile`. The stored profile keeps `identity` and
* `style` as separate fields, and the handler joins them into a single
* `aboutMe` string before answering, so the wire shape and the stored shape
* are genuinely different types rather than one being an alias of the other.
* `skills` is always present on the wire, defaulted to an empty array.
*/
export type ProfileResponse = {
aboutMe: string
preferences: LearningProfile['preferences']
skills: LearningProfile['skills']
}
/** GET /api/audiobook/status — whether the narration engine is installed. */
export type AudiobookStatus = {
installed: boolean
missing: { model: boolean; ffmpeg: boolean }
downloadSize: number
}