Skip to content

Commit a40fec3

Browse files
committed
Extract bounded catalog read with one error mapping
1 parent 69aa8ab commit a40fec3

1 file changed

Lines changed: 70 additions & 66 deletions

File tree

‎src/provider/bounded-model-catalog.ts‎

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,68 @@ export type CatalogDiscoveryState =
1212
| { readonly status: "unavailable"; readonly message: string }
1313
| { readonly status: "malformed"; readonly message: string };
1414

15+
function catalogErrorMessage(error: unknown): string {
16+
return error instanceof Error ? error.message : String(error);
17+
}
18+
19+
function declaredCatalogBytes(response: Response): number | undefined {
20+
const raw = response.headers.get("content-length");
21+
if (raw === null || raw.length === 0) return undefined;
22+
const n = Number(raw);
23+
if (!Number.isFinite(n) || n < 0) return undefined;
24+
return n;
25+
}
26+
27+
async function readBoundedCatalogText(
28+
response: Response,
29+
args: { readonly maxBytes: number; readonly oversizeMessage: string },
30+
): Promise<
31+
| { readonly ok: true; readonly text: string }
32+
| { readonly ok: false; readonly message: string }
33+
> {
34+
const declared = declaredCatalogBytes(response);
35+
if (declared !== undefined && declared > args.maxBytes) {
36+
await response.body?.cancel().catch(() => undefined);
37+
return { ok: false, message: args.oversizeMessage };
38+
}
39+
40+
try {
41+
const body = response.body;
42+
if (body === null) {
43+
const text = await response.text();
44+
if (new TextEncoder().encode(text).byteLength > args.maxBytes) {
45+
return { ok: false, message: args.oversizeMessage };
46+
}
47+
return { ok: true, text };
48+
}
49+
50+
const reader = body.getReader();
51+
const chunks: Uint8Array[] = [];
52+
let total = 0;
53+
for (;;) {
54+
const { done, value } = await reader.read();
55+
if (done) break;
56+
if (value === undefined) continue;
57+
total += value.byteLength;
58+
if (total > args.maxBytes) {
59+
await reader.cancel().catch(() => undefined);
60+
return { ok: false, message: args.oversizeMessage };
61+
}
62+
chunks.push(value);
63+
}
64+
65+
const buffer = new Uint8Array(total);
66+
let offset = 0;
67+
for (const chunk of chunks) {
68+
buffer.set(chunk, offset);
69+
offset += chunk.byteLength;
70+
}
71+
return { ok: true, text: new TextDecoder().decode(buffer) };
72+
} catch (error) {
73+
return { ok: false, message: catalogErrorMessage(error) };
74+
}
75+
}
76+
1577
export function createBoundedModelCatalog(args: {
1678
baseURL: string;
1779
seedIds: readonly string[];
@@ -32,14 +94,6 @@ export function createBoundedModelCatalog(args: {
3294
let inflight: Promise<readonly string[]> | undefined;
3395
let snapshot: readonly string[] | undefined;
3496

35-
function declaredCatalogBytes(response: Response): number | undefined {
36-
const raw = response.headers.get("content-length");
37-
if (raw === null || raw.length === 0) return undefined;
38-
const n = Number(raw);
39-
if (!Number.isFinite(n) || n < 0) return undefined;
40-
return n;
41-
}
42-
4397
function oversizeMessage(kind: "bytes" | "models"): string {
4498
if (kind === "bytes") {
4599
return `${catalogLabel} catalog exceeds ${String(maxBytes)} bytes`;
@@ -53,66 +107,16 @@ export function createBoundedModelCatalog(args: {
53107
| { readonly ok: true; readonly value: unknown }
54108
| { readonly ok: false; readonly message: string }
55109
> {
56-
const declared = declaredCatalogBytes(response);
57-
if (declared !== undefined && declared > maxBytes) {
58-
await response.body?.cancel().catch(() => undefined);
59-
return { ok: false, message: oversizeMessage("bytes") };
60-
}
61-
62-
const body = response.body;
63-
if (body === null) {
64-
try {
65-
const text = await response.text();
66-
if (new TextEncoder().encode(text).byteLength > maxBytes) {
67-
return { ok: false, message: oversizeMessage("bytes") };
68-
}
69-
const value: unknown = JSON.parse(text);
70-
return { ok: true, value };
71-
} catch (error) {
72-
return {
73-
ok: false,
74-
message: error instanceof Error ? error.message : String(error),
75-
};
76-
}
77-
}
78-
79-
const reader = body.getReader();
80-
const chunks: Uint8Array[] = [];
81-
let total = 0;
110+
const text = await readBoundedCatalogText(response, {
111+
maxBytes,
112+
oversizeMessage: oversizeMessage("bytes"),
113+
});
114+
if (!text.ok) return text;
82115
try {
83-
for (;;) {
84-
const { done, value } = await reader.read();
85-
if (done) break;
86-
if (value === undefined) continue;
87-
total += value.byteLength;
88-
if (total > maxBytes) {
89-
await reader.cancel().catch(() => undefined);
90-
return { ok: false, message: oversizeMessage("bytes") };
91-
}
92-
chunks.push(value);
93-
}
94-
} catch (error) {
95-
return {
96-
ok: false,
97-
message: error instanceof Error ? error.message : String(error),
98-
};
99-
}
100-
101-
const buffer = new Uint8Array(total);
102-
let offset = 0;
103-
for (const chunk of chunks) {
104-
buffer.set(chunk, offset);
105-
offset += chunk.byteLength;
106-
}
107-
108-
try {
109-
const value: unknown = JSON.parse(new TextDecoder().decode(buffer));
116+
const value: unknown = JSON.parse(text.text);
110117
return { ok: true, value };
111118
} catch (error) {
112-
return {
113-
ok: false,
114-
message: error instanceof Error ? error.message : String(error),
115-
};
119+
return { ok: false, message: catalogErrorMessage(error) };
116120
}
117121
}
118122

@@ -130,7 +134,7 @@ export function createBoundedModelCatalog(args: {
130134
} catch (error) {
131135
return {
132136
status: "unavailable",
133-
message: error instanceof Error ? error.message : String(error),
137+
message: catalogErrorMessage(error),
134138
};
135139
}
136140

0 commit comments

Comments
 (0)