首屏包里带着整个 zod 运行时(~100KB),来路有三条,缺一条都挪不出去(逐条撤回实测过): - 契约包没有 sideEffects: false,入口从桶里取任何一个值都会把所有 schema 模块带上。 契约包里没有顶层副作用(核过),加上之后 rolldown 才能按导出摇树 - roles.ts 里的 USER_TYPE / toAdminType 是入口要的,同文件却定义了两个 z.enum。 schema 挪到 admin.ts(唯一用处),roles.ts 文件头写明不许 import zod - 启动就要调的 getWebsiteConfig / getAILoginSummary 在 oj/api.ts 里,而那个文件同步 import 了题目详情、提交详情两个 schema。这两个函数挪到 shared/api.ts;那两道 闸门保持同步 —— submissionDetailSchema 有 .catch(),放行原文和解析结果不等价 - /me 的契约闸门改成 contractDeferred:先放行原文,schema 按需加载后在后台核、只记 日志。userProfileSchema 没有 default / transform,两者等价。动态 import 的目标是 单独的 shared/profileSchema.ts —— 直接 import 契约桶会让 rolldown 重排共享 chunk, 实测首屏反而涨到 1811KB 浏览器实跑核过:登录后 profile 正常加载、profileSchema 按需拉取、无控制台报错; 故意喂一份不符的数据,后台闸门照常打出 [契约] 分歧日志。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
import type { LoginSummary, Quote, WebsiteConfig } from "@oj2/contract"
|
||
import api from "utils/api"
|
||
import { contractDeferred } from "utils/contract"
|
||
import type { Profile, Tag } from "utils/types"
|
||
|
||
export function login(data: { username: string; password: string }) {
|
||
return api.post("auth/login", data)
|
||
}
|
||
|
||
export function signup(data: {
|
||
username: string
|
||
email: string
|
||
password: string
|
||
}) {
|
||
return api.post("users", data)
|
||
}
|
||
|
||
export function logout() {
|
||
return api.delete("auth/session")
|
||
}
|
||
|
||
export async function getProfile(
|
||
username: string = "",
|
||
): Promise<Profile | null> {
|
||
const endpoint = username ? `profiles/${encodeURIComponent(username)}` : "me"
|
||
const response = await api.get<unknown>(endpoint)
|
||
if (response === null) return null
|
||
// 形状与契约一致,不再逐字段搬运。走契约闸门而不是裸 parse():原来这里是
|
||
// `userProfileSchema.parse(response) as Profile`,`as` 把校验结果又断言回本地
|
||
// 类型(Profile 把 user 收窄成 SessionUser、acmProblemsStatus 收窄成具体形状),
|
||
// 形状对不上时页面白屏。现在记一条分歧日志后放行原始数据。
|
||
// 用 deferred 版:启动就要拉 /me,同步校验会把 zod 整个打进首屏包
|
||
return contractDeferred(
|
||
"GET /profiles/:username",
|
||
() => import("./profileSchema").then((m) => m.userProfileSchema),
|
||
response,
|
||
)
|
||
}
|
||
|
||
export function getProblemTagList() {
|
||
return api.get<Tag[]>("problem-tags")
|
||
}
|
||
|
||
export function getHitokoto() {
|
||
return api.get<Quote>("quotes/random")
|
||
}
|
||
|
||
export function getClassUsernames(classroom: string) {
|
||
return api.get<string[]>(`classes/${encodeURIComponent(classroom)}/usernames`)
|
||
}
|
||
|
||
// 下面两个是启动就要调的(config / loginSummary 两个 store),所以放在 shared 而不是
|
||
// oj/api.ts:oj/api.ts 同步 import 了题目详情、提交详情两个 schema(它们的契约闸门必须
|
||
// 同步,submissionDetailSchema 里有 `.catch()`,放行原文和解析结果不等价),入口一旦
|
||
// 引到它,zod 整个就进了首屏包。
|
||
export function getWebsiteConfig() {
|
||
return api.get<WebsiteConfig>("site")
|
||
}
|
||
|
||
export function getAILoginSummary() {
|
||
return api.get<LoginSummary>("ai/login-summary")
|
||
}
|