fix(web): stale-closure + focus trap + 去边界强转 + a11y/性能打磨 + 类型化客户端
P1-6 useStyleLearn/useKimiOauth 修 stale-closure 依赖。 P1-7 CommandPalette/Drawer 加 focus trap(新 lib/a11y/focusTrap)。 P1-8 SSE 解析与 server.ts 去 as 强转改类型守卫/运行时校验。 P2 删冗余强转、开 noUncheckedIndexedAccess、Toast 清理+稳定 key、列表 key 稳定化、 rel=noopener、useMemo 大文本、ConflictCard role=group、useInjection 加锁、 client.test 真断言。 codegen 重生成 schema.d.ts + 消费 DimensionEntry/ReviewConflictView 强类型。
This commit is contained in:
@@ -16,13 +16,29 @@ import type {
|
||||
WorldEntityListResponse,
|
||||
} from "./types";
|
||||
|
||||
// 解析 JSON 响应体并做最小运行时校验:非 null 对象/数组才放行,
|
||||
// 否则抛结构化错误(后端契约保证为 JSON 对象;防御被代理/网关污染的非 JSON 响应)。
|
||||
// 校验通过后 narrow 到 T(OpenAPI 生成类型为权威形状,此处仅守边界非 null)。
|
||||
async function parseJsonBody<T>(res: Response, path: string): Promise<T> {
|
||||
let body: unknown;
|
||||
try {
|
||||
body = await res.json();
|
||||
} catch {
|
||||
throw new Error(`响应非 JSON:${path}`);
|
||||
}
|
||||
if (typeof body !== "object" || body === null) {
|
||||
throw new Error(`响应体形状异常(期望对象):${path}`);
|
||||
}
|
||||
return body as T;
|
||||
}
|
||||
|
||||
// 服务端只读取数据(Server Components)。失败时抛出,由页面边界处理。
|
||||
async function getJson<T>(path: string): Promise<T> {
|
||||
const res = await fetch(`${serverApiBase()}${path}`, { cache: "no-store" });
|
||||
if (!res.ok) {
|
||||
throw new Error(`请求失败 ${res.status}: ${path}`);
|
||||
}
|
||||
return (await res.json()) as T;
|
||||
return parseJsonBody<T>(res, path);
|
||||
}
|
||||
|
||||
// 像上面一样读取,但 404(无指纹)返回 null 而非抛出(首学前页面照常渲染)。
|
||||
@@ -32,7 +48,7 @@ async function getJsonOrNull<T>(path: string): Promise<T | null> {
|
||||
if (!res.ok) {
|
||||
throw new Error(`请求失败 ${res.status}: ${path}`);
|
||||
}
|
||||
return (await res.json()) as T;
|
||||
return parseJsonBody<T>(res, path);
|
||||
}
|
||||
|
||||
export async function fetchProjects(): Promise<ProjectListResponse> {
|
||||
|
||||
Reference in New Issue
Block a user