fix(rest): emit the projected export header on an empty result set (#3547)#3649
Merged
Merged
Conversation
…3547) `GET /data/:object/export` wrote a zero-byte file whenever the query matched no rows — the header was only ever written alongside the first data chunk. The `getReadableFields` projection derives the readable column set from schema + context, so it is known even with zero rows: "export columns don't depend on row content" is only true if it also holds at zero rows. The header is emitted only when the column set is AUTHORITATIVE — the security service's readable projection, or an explicit `?fields=` request (the caller named the columns, so echoing them discloses nothing new). When the header is schema-derived and the projection was unavailable, the export stays headerless as before: the masked-row fallback has no rows to narrow with, and writing the full schema header would name FLS-hidden columns. `header=false` still suppresses the header in every case. Tests: empty result emits the projected header (csv + xlsx); no projection stays headerless; explicit `?fields=` is echoed; `header=false` wins. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZ2BGusRo58ZW8FMkDhGTb
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
3 tasks
os-zhuang
marked this pull request as ready for review
July 27, 2026 12:58
This was referenced Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
#3547 的收尾。PR #3561 已落地
getReadableFields(object, context)查询面,让 export 的列投影从「反推 masked 行」(#3498)改为「向 security 服务权威查询」。但 #3547 验收项里的「不受空结果影响」还差最后一步:零行结果时,
GET /data/:object/export至今写出的是零字节文件——表头只在首个数据块旁边写一次(while循环内),而首块为空就直接break,表头从未落笔。「导出列不依赖行内容」这句话,只有在零行时也成立才算真的成立。而列投影本来就由 schema + context 推导,零行时它照样知道该出哪些列。
改动
packages/rest/src/rest-server.ts—— 流式循环结束后,在列集权威时补写表头:readableProjected)或显式?fields=(调用方自己点名的列,回显不泄露任何新信息)。空导出因此成为一份可用的导入模板。fieldsFromSchema && !readableProjected时,masked 行回退路径没有行可收窄,此时写出完整 schema 表头会把 FLS 隐藏列的列名暴露出来——正是 跟踪:UI 操作按钮与 apiMethods 白名单一致性契约落地(#3026 设计定稿) #3391 要堵的泄露。该路径维持今天的行为。header=false在任何情况下仍然抑制表头。json无表头概念,[]本就正确,不受影响。契约 / 兼容
无契约变更。唯一的行为变化是「空结果 + 权威列集」从零字节文件变为一行表头;无 security 服务的部署行为逐字节不变。
测试
packages/rest/src/export-integration.test.ts新增 5 例(#3547 块):getReadableFields → undefined(投影不可用)→ 保持无表头,不泄露 FLS 隐藏列名;?fields=→ 按请求回显;header=false→ 无表头;rowCount === 1)。全量:
@objectstack/rest26 文件 / 399 测试通过;pnpm turbo build --filter=@objectstack/restCJS/ESM/DTS 全绿。关联
Closes #3547(本 PR 补齐验收项「导出列不受 null 值/空结果影响」的空结果一半;null 值一半由 #3561 完成)。关联 #3391、#3498、#3561。
🤖 Generated with Claude Code
https://claude.ai/code/session_01KZ2BGusRo58ZW8FMkDhGTb
Generated by Claude Code