Skip to content

fix(client): data.find 的 top/skip 按存在性发射,limit: 0 不再被静默丢弃 (#6485) - #6578

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-6485-find-pagination-presence
Aug 8, 2026
Merged

fix(client): data.find 的 top/skip 按存在性发射,limit: 0 不再被静默丢弃 (#6485)#6578
os-project-manager merged 1 commit into
mainfrom
claude/issue-6485-find-pagination-presence

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #6485

两处 findObjectStackClient.data.findScopedProjectClient.data.find 的逐字节副本)都以真值判断发射分页参数,而其上方十行的 canonical normalizer 早已按存在性判断(if (v2.limit != null))。于是 0 通过了 normalizer,又被发射端丢掉。两处一并改为 != null

行号漂移

卡片正文记的是 eb7613c 上的 :4224/:4225:4908/:4909;两条分诊评论分别在 b7d3be4 读到 :4104/:4105:4778/:4779,在 3a1d9c7 读到 :4225/:4909。本次在 53ef05744按内容定位,落点回到 :4224/:4225:4908/:4909 —— 与卡片正文数字重合纯属巧合,中间三次读数各不相同。所以按 pattern 找,不要信任何一处记下的行号。

前提复现(改动前,在 origin/main 上量的)

新增断言直接跑在未修复的源码上,6 条红:

FAIL  canonical zero: { limit: 0 } → `top=0` on both copies
AssertionError: expected '' to be 'top=0'

{ limit: 0 } 发出的是空查询串 —— 一个 top 参数都没有。

服务端 top=0 到底怎么处理:先测后改

这半边是本卡的关键。客户端把 top=0 发出去,只有在服务端真的认这个值时才算修好。三层都实测过,不是读代码推的:

top=0 的实测行为
REST 列表路由 → ObjectStackProtocolImplementation.findData 既不拒绝也不忽略top 折叠进 limitNumber('0') 得 0,{ limit: 0 } 原样转发给引擎;信封给出 total: 0, hasMore: false
SqlDriver.find(默认文件型 SQLite 数据源背后的驱动,也是 Postgres/MySQL 部署用的那个) 按存在性分页 —— LIMIT 0零行
TursoRemoteTransport 同样按存在性 —— LIMIT ? 绑 0,零行

探针输出:

PROBE top="0" => { engineFindOptions: { limit: 0 }, result: { records: 0, total: 0, hasMore: false } }
PROBE sql-driver => {"limit0_rows":0,"limit2_rows":2,"noLimit_rows":3}

即派发单列的三种结局里的第一种:服务端返回零记录,修复如描述般正确。

顺带纠正卡片正文一处措辞:丢掉 top 之后拿到的不是「服务端默认页大小」—— 这条 GET 列表路由没有默认页大小。实测 no top 一栏返回 3/3 全量。所以 find('task', { limit: 0 }) 的旧行为是「要零条、给全部」,比「给一页」更糟。

offset: 0 / skip: 0:这半边是一致性修改,没有行为后果

明说,不包装成 bug fix:skip=0 本就是服务端默认值,发不发这个参数请求含义相同。改它的理由只有一个 —— 一个发射端不该对同一对参数持两套规则。

测试:结构上保证半边修复必红

复用 #6322 建立的 driveBoth 表格 —— 同一组 options 同时驱动两份 find,比对同一个期望查询串。新增 5 行表项({ limit: 0 } / { offset: 0 } / { top: 0 } / { skip: 0 } / { limit: 0, offset: 0 }),外加一条性质断言:{ limit: 0 }{} 在线上必须可区分(旧代码里两者都发空串,正是这个坍缩本身)。

反向验证:先写下预测,再跑

方向 预测红 实测红 失败断言
两份都回退(= origin/main 6 6 expect(direct) L1401
只回退主 client 一份 6 6 expect(direct) L1401
只回退 ScopedProjectClient 一份 6 6 expect(scoped) L1402

第三行是本卡要的那条约束:只修一份,另一份的 scoped 断言就红。6 条断言没有一条在两个方向都是绿的,所以没有需要从红计数里剔除的「不变量 pin」。

门禁(真实输出,非声明)

  • pnpm --filter @objectstack/client testTest Files 21 passed (21) / Tests 269 passed (269)
  • pnpm --filter @objectstack/client typechecktsc --noEmit 通过;check:test-typecheck: OK — 0 file(s) / 0 error(s)
  • pnpm lint → 无输出(干净)
  • lint.yml 枚举的全部 check:*:37 条根级门禁 + 11 条 spec 门禁全 PASS,含 check:query-options-erasurecheck:route-envelopecheck:error-code-casingcheck:engine-double-contractcheck:nul-bytes
  • turbo run typecheck --filter='./packages/*' --filter='./packages/*/*' --filter='./apps/*'120 successful, 120 total
  • check:i18n / check:i18n-coverage 首轮报的是前置条件未满足(本 worktree 未构建 CLI),不是发现;补齐构建后两条均 PASS(12 config(s), 660 baselined, none new
  • 控制字节自查:grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' 三个改动文件均无命中;check:nul-bytes OK

Changeset

@objectstack/client: patch。判断依据:这是缺陷修复,不新增 API、不新增选项、不移除能力;行为变化只落在 limit: 0 / top: 0 这一组此前返回明确错误答案的输入上。变更正文把 wire 行为变化写明,以便进入 release notes。

范围外发现

🤖 Generated with Claude Code

https://claude.ai/code/session_017uFVNMmTxLpmfQYiuKM1Yx


Generated by Claude Code

两处 `find`(`ObjectStackClient.data.find` 与 `ScopedProjectClient.data.find`
的逐字节副本)都以**真值**判断发射分页参数,而其上方十行的 canonical
normalizer 早已按**存在性**判断(`if (v2.limit != null)`)。于是 `0` 通过了
normalizer,又被发射端丢掉。两处一并改为存在性判断。

`find('task', { limit: 0 })` 此前到达服务端时**完全没有 `top` 参数**。GET 列表
路由没有默认页大小,所以缺失 `top` 返回的是**全量**匹配集:请求「不要记录」的
调用方拿到了每一条记录,HTTP 200,无任何告警。

方向是先测后改,而非假定:REST 列表路由(`findData`)既不拒绝也不忽略 `top=0`,
它折叠为 `limit: 0` 转发给引擎;`SqlDriver.find`(默认 SQLite 数据源背后的驱动)
同样按存在性分页,语句带 `LIMIT 0`,返回零行。

`offset: 0` / `skip: 0` 同样被丢弃,但那半边是一致性修改、无行为后果——`skip=0`
本就是服务端默认值。改它是因为一个发射端不该对同一对参数持两套规则。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uFVNMmTxLpmfQYiuKM1Yx
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 8, 2026 5:47am

Request Review

@github-actions github-actions Bot added the size/m label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/client.

14 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/client)
  • content/docs/api/client-sdk.mdx (via @objectstack/client)
  • content/docs/api/data-flow.mdx (via @objectstack/client)
  • content/docs/api/environment-routing.mdx (via @objectstack/client)
  • content/docs/api/error-catalog.mdx (via @objectstack/client)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/client)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/client)
  • content/docs/kernel/runtime-services/index.mdx (via packages/client)
  • content/docs/permissions/authentication.mdx (via @objectstack/client)
  • content/docs/plugins/packages.mdx (via @objectstack/client)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/client)
  • content/docs/releases/implementation-status.mdx (via @objectstack/client)
  • content/docs/releases/v16.mdx (via @objectstack/client)
  • content/docs/releases/v17.mdx (via @objectstack/client)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding][client] data.find's pagination params are emitted on truthiness, so { limit: 0 } is dropped exactly like the limit bug #6322 fixed

2 participants