v0.3.0
代理层现在收得下 multipart 上传与裸字节直传。ABP [FromForm] 的导入端点此前拿到的是一个没有正文的请求。
修复
AbpProxyRequest.body 的类型是 string,而 abpFetch 用 typeof init?.body === "string" ? init.body : undefined 收窄调用方的 body——传 FormData 不报错,静默变成空 body。四层链路(abpFetch → abpRequestFn 的 zod schema → AbpProxyRequest → proxy.send)都把 body 钉在 string 上。
新增
AbpProxyRequest.body 拓宽为 string | Uint8Array | ArrayBuffer | FormData,新导出类型 AbpProxyBody。
判据是可重发而不是「是不是二进制」:代理的核心能力是 401→刷新→重放与幂等重试,两者都要把同一个 body 再发一次。ReadableStream 刻意排除,并在客户端与代理两处显式拒绝——它只能消费一次,收下它会让那两条路径静默退化成「重放一个空正文」。
新增 server function abpUploadFn,二进制以原生 multipart 过桥,不进 JSON 载荷。这是量过之后的选择:
| 走法 | 实测 |
|---|---|
Uint8Array 交给 seroval |
1MB 反序列化抛 SerovalDeserializationError(512KB 尚可) |
| base64 成字符串 | 10MB 能过,但文件变 13.3MB 字符串、两端各解析一次 JSON |
| 原生 multipart | 文件字节全程不进 JSON |
TanStack Start 要求整个 payload 就是 FormData,混不进 JSON 对象,故为第二个 server fn。文本那条路径的 zod schema 一个字未动,所有既有 CRUD 调用不受影响。
createAbpProxy 新增 maxBodyBytes(默认 10MB),在任何网络动作之前拦截。
一个会让人查很久的坑
body 是 FormData 时,调用方的 content-type 现在会被丢弃。它带的是调用方那份 FormData 的 boundary,而传输层重新编码时会生成新的;透传旧值会让上游按错的分隔符解析,一个字段都读不出来——症状是「请求到了、参数全空」,不是报错。content-type 本就在转发白名单内,所以这条不处理必然踩。
使用
调用方无需改动写法,abpFetch 对外仍是 typeof fetch:
const form = new FormData();
form.append("file", file, file.name);
await abpFetch("/api/app/books/import", { method: "POST", body: form });裸字节直传同样可用(content-type 按需自带):
await abpFetch("/api/app/files/1", {
method: "PUT",
headers: { "content-type": "application/octet-stream" },
body: bytes,
});升级须知
@jcoder-stack/registry 的分发物变了:jc-abp add app-shell 装到的 src/api/abp-fetch.ts 是新版,且 jc-abp init 会多铺一个 src/auth/upload-payload.ts。已有项目要吃到这个能力需重新 add / init。AbpProxyRequest.body 是加法拓宽,既有调用无需改动。
验证
97 个测试文件 / 836 用例(新增 14 条)、typecheck(含 @ts-expect-error 类型契约)、lint、三处 build 全通过;registry 分发物已随块源码重建。