Skip to content

Commit 785b1c1

Browse files
author
Transcript-Fetch-Admin
committed
feat: response models and normalizers
1 parent 1bc717d commit 785b1c1

1 file changed

Lines changed: 186 additions & 0 deletions

File tree

‎src/models.ts‎

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/**
2+
* Typed response models, matching the TranscriptFetch API schemas.
3+
*
4+
* The API mixes snake_case (transcript/list envelopes) and camelCase (video
5+
* list items). The `normalize*` helpers accept either wire shape and return
6+
* clean camelCase objects, so consumers never see the inconsistency.
7+
*/
8+
9+
/** Credit + byte accounting returned alongside every successful response. */
10+
export interface Usage {
11+
creditsSpent: number;
12+
/** null for unlimited (admin) accounts. */
13+
balance: number | null;
14+
bytes: number;
15+
}
16+
17+
/** A single timestamped caption cue. */
18+
export interface Segment {
19+
start: number;
20+
duration: number;
21+
text: string;
22+
}
23+
24+
/** A single video's transcript. */
25+
export interface Transcript {
26+
kind: "transcript";
27+
videoId: string;
28+
title: string | null;
29+
text: string | null;
30+
segments: Segment[];
31+
usage: Usage | null;
32+
}
33+
34+
/** A video reference from a channel/playlist/search list (metadata only). */
35+
export interface Video {
36+
videoId: string;
37+
title: string | null;
38+
thumbnailUrl: string | null;
39+
duration: number | null;
40+
channel: string | null;
41+
}
42+
43+
/** A paginated list of videos. */
44+
export interface VideoList {
45+
kind: "video_list";
46+
source: string;
47+
videos: Video[];
48+
nextCursor: string | null;
49+
usage: Usage | null;
50+
}
51+
52+
/** One video's result inside a batch response. */
53+
export interface BatchResult {
54+
videoId: string;
55+
/** ok | no_transcript | blocked | error | null */
56+
outcome: string | null;
57+
title: string | null;
58+
text: string | null;
59+
segments: Segment[] | null;
60+
cached: boolean;
61+
bytes: number;
62+
}
63+
64+
/** Result of a batch fetch. */
65+
export interface BatchResponse {
66+
kind: "transcript_batch";
67+
results: BatchResult[];
68+
usage: Usage | null;
69+
}
70+
71+
/** The public health-check body. */
72+
export interface Health {
73+
status: string;
74+
service: string;
75+
version: string;
76+
time: string;
77+
}
78+
79+
// ── Wire helpers ──────────────────────────────────────────────────────────────
80+
81+
type Wire = Record<string, unknown>;
82+
83+
function obj(value: unknown): Wire {
84+
return value && typeof value === "object" ? (value as Wire) : {};
85+
}
86+
87+
/** Read the first present key (supports both snake_case and camelCase wire names). */
88+
function pick(source: Wire, ...keys: string[]): unknown {
89+
for (const key of keys) {
90+
if (source[key] !== undefined && source[key] !== null) return source[key];
91+
}
92+
return undefined;
93+
}
94+
95+
function str(value: unknown): string {
96+
return typeof value === "string" ? value : "";
97+
}
98+
99+
function strOrNull(value: unknown): string | null {
100+
return typeof value === "string" ? value : null;
101+
}
102+
103+
function numOrNull(value: unknown): number | null {
104+
return typeof value === "number" && Number.isFinite(value) ? value : null;
105+
}
106+
107+
function num(value: unknown, fallback = 0): number {
108+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
109+
}
110+
111+
function normalizeSegment(raw: unknown): Segment {
112+
const s = obj(raw);
113+
return { start: num(s.start), duration: num(s.duration), text: str(s.text) };
114+
}
115+
116+
function normalizeSegments(raw: unknown): Segment[] {
117+
return Array.isArray(raw) ? raw.map(normalizeSegment) : [];
118+
}
119+
120+
function normalizeUsage(env: Wire): Usage | null {
121+
const raw = env.usage;
122+
if (!raw || typeof raw !== "object") return null;
123+
const u = raw as Wire;
124+
return {
125+
creditsSpent: num(pick(u, "creditsSpent", "credits_spent")),
126+
balance: numOrNull(pick(u, "balance")),
127+
bytes: num(pick(u, "bytes")),
128+
};
129+
}
130+
131+
function normalizeVideo(raw: unknown): Video {
132+
const v = obj(raw);
133+
return {
134+
videoId: str(pick(v, "videoId", "video_id")),
135+
title: strOrNull(pick(v, "title")),
136+
thumbnailUrl: strOrNull(pick(v, "thumbnailUrl", "thumbnail_url")),
137+
duration: numOrNull(pick(v, "duration")),
138+
channel: strOrNull(pick(v, "channel")),
139+
};
140+
}
141+
142+
/** Parse a `{ data, usage }` transcript envelope. */
143+
export function normalizeTranscript(env: Wire): Transcript {
144+
const d = obj(env.data);
145+
return {
146+
kind: "transcript",
147+
videoId: str(pick(d, "videoId", "video_id")),
148+
title: strOrNull(pick(d, "title")),
149+
text: strOrNull(pick(d, "text")),
150+
segments: normalizeSegments(d.segments),
151+
usage: normalizeUsage(env),
152+
};
153+
}
154+
155+
/** Parse a `{ data, usage }` video-list envelope. */
156+
export function normalizeVideoList(env: Wire): VideoList {
157+
const d = obj(env.data);
158+
const videos = Array.isArray(d.videos) ? d.videos.map(normalizeVideo) : [];
159+
return {
160+
kind: "video_list",
161+
source: str(pick(d, "source")),
162+
videos,
163+
nextCursor: strOrNull(pick(d, "nextCursor", "next_cursor")),
164+
usage: normalizeUsage(env),
165+
};
166+
}
167+
168+
/** Parse a `{ data, usage }` batch envelope. */
169+
export function normalizeBatch(env: Wire): BatchResponse {
170+
const d = obj(env.data);
171+
const rawResults = Array.isArray(d.results) ? d.results : [];
172+
const results: BatchResult[] = rawResults.map((raw) => {
173+
const r = obj(raw);
174+
const segments = r.segments;
175+
return {
176+
videoId: str(pick(r, "videoId", "video_id")),
177+
outcome: strOrNull(pick(r, "outcome")),
178+
title: strOrNull(pick(r, "title")),
179+
text: strOrNull(pick(r, "text")),
180+
segments: Array.isArray(segments) ? normalizeSegments(segments) : null,
181+
cached: pick(r, "cached") === true,
182+
bytes: num(pick(r, "bytes")),
183+
};
184+
});
185+
return { kind: "transcript_batch", results, usage: normalizeUsage(env) };
186+
}

0 commit comments

Comments
 (0)