Repository navigation
Commit 7bb8084
fix(client): data.find refuses a select that names no field before the request (#26946)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XBJqmquET4esJZNQWTR5J1e)_
Fixes #26910
Clause-②: no
Metadata-protocol: none
The `Clause-②` measure: the calls refused are exactly the calls the list
door already refuses with `400 VALIDATION_ERROR` (the parity test below
sends the same request to the real door), and the caller gets the same
`code`, `httpStatus` and message, only without the round trip. No call
that was served is refused, nothing is widened, and no type changes
(`select` / `fields` stay `string[]`; no non-empty tuple narrowing was
added).
## 维护者速读
- 改了什么:SDK 的 `data.find` 遇到 `select: []`(或 `fields:
[]`、成员全为空白)时,在发请求之前就拒绝,报错与服务端列表门完全相同(`VALIDATION_ERROR` / 400 / 同一句话)。
- 为什么改:之前空数组为真值,SDK 会发出 `?select=`,白跑一趟网络再拿到
400;改成调用点直接报错。参数绝不丢弃,丢弃会变成读全部列。
- 风险与回滚:只影响本来就会被服务端 400 拒绝的调用;回滚即还原 `packages/client/src/index.ts` 中的一个
helper 及两处调用。
- 先看:`packages/client/src/index.ts` 的
`projectionSelectParam`;`packages/client/src/client.hono.test.ts`
新增的对等测试。
- 两分钟验证:`pnpm --filter @objectstack/client exec vitest run
src/client.test.ts src/client.hono.test.ts`,预期全部通过,其中含 4 条空 projection
拒绝用例与 1 条真实门对等用例。
- 需要你做的:无,席位落地。
## What changes
- **One shared helper, both copies.** `projectionSelectParam`
(`packages/client/src/index.ts`) builds the `select` value both
`ObjectStackClient.data.find` and `ScopedEnvironmentClient.data.find`
put on the wire. It applies the door's own rule to the exact value the
client would have sent (split on commas, trim, at least one name) and,
when that names no field, throws before any request with `code:
'VALIDATION_ERROR'`, `httpStatus: 400` and the door's sentence (`Query
parameter 'select' names no field; got "". The query was not run: ...`).
- **Never dropped.** An empty projection is refused, not omitted: an
absent `select` reads every column, the widening the door refuses.
- **Where the sentence comes from.** The door's copy lives in
`projectionFieldsValue`, a module-private function in
`packages/metadata-protocol/src/protocol.ts`. No shared copy exists in a
package the client may import (`@objectstack/client` depends on
`@objectstack/core` and `@objectstack/spec` only; spec is outside this
card's file surface, and the spec's `HttpFindQueryParamsSchema.select`
message is a different sentence). So the client restates it, and
`client.hono.test.ts` holds the restated copy equal to the real door's
answer.
- **Sweep.** Every `select` emitter in `packages/client/src`: exactly
the two `data.find` copies (`queryParams.set('select', ...)` at the two
sites, both reached by the canonical `fields` mapping too). No other
method builds `select` or another projection parameter from a truthy
test: `data.query` sends a QueryAST body verbatim, and
`QueryBuilder.select()` writes `fields` into that body (the POST door
applies the same refusal server-side).
- Changeset: `patch` on `@objectstack/client`.
## Tests
- **Red first, on `main` `8074feb6`.** A probe drove both copies with a
stubbed `fetch`: `{ select: [] }` and `{ fields: [] }` requested
`.../data/task?select=` (scoped:
`.../environments/env-1/data/task?select=`), `{ select: ['', ' '] }`
requested `?select=%2C+`. The new `client.test.ts` cases went red there:
`4 failed | 1 passed` with `promise resolved ... instead of rejecting`.
- **Green, on `8efd86c3`.** `client.test.ts`: four rows (`select: []`,
`fields: []`, `select: ['', ' ']`, `fields: [' ']`), each on both
copies, assert the rejection's `code`, `httpStatus` and message and that
`fetch` was never called. Controls: `select: ['a','b']` and `fields:
['a','b']` send `select=a%2Cb`; `select: ['a', '']` still sends
`select=a%2C` (the door serves it); the existing `no options at all` row
still sends no parameter.
- **Parity with the real door.** `client.hono.test.ts` sends `?select=`
and `?select=%2C+` raw through the client's transport against the real
REST stack, then asserts `data.find` refuses `[]` and `['', ' ']` with
the identical `message`, `code` and `httpStatus`.
- **Ablation (one-shot, not kept).** `scripts/ablation-replace.mjs`
drifted the restated sentence's last clause in `index.ts`; the parity
test went red (`1 failed`); restore proved by blob `e553346c4fe0` equal
to `HEAD` and an empty `git diff HEAD`.
- `@objectstack/client`: `pnpm test` 52 files, 678 tests passed;
`typecheck` exit 0 (including `check:test-typecheck`); `eslint
packages/client --no-inline-config` exit 0.
## Acceptance notes
- `groupBy` is emitted on the same truthy test (`groupBy: []` sends
`?groupBy=`). It is not a projection and the card names only
projections, so it is untouched here.
- A JS caller passing a string instead of the declared `string[]` hits
`select.join is not a function`, as before; nothing about that path
changes.
- No open pull request titled for the client was found (`is:open client
in:title`, 0 hits), so no overlap on `packages/client/src/index.ts` was
seen.
## Verification
<details><summary>Derived gate families on 8efd86c (59 derived, 58 run,
1 NOT MEASURED)</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 59 families on this diff; `--ran`
reconciled 59 accounted, 58 run with exit 0, 1 NOT MEASURED.
- NOT MEASURED: `pnpm check:dual-build-cjs-loads` exited 3 (PREREQUISITE
NOT MET: it needs every package's `dist/`, i.e. a full workspace build).
CI's `Lint & Repo Gates` runs it on this PR.
- `node scripts/check-plugin-teardown-shape.mjs --self-test` first
exited 3 on the shallow clone; after fetching its pinned fixture commit
it exited 0 (48 cases).
- `pnpm --filter @objectstack/spec run check:skill-examples` first
exited 3 (client-react not built); after building it, exit 0 (267 prose
examples type-check, 23 blocks on the client SDK surface).
- Every other family: exit 0.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5mTFyYsPjawe7Bv7x5)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5mTFyYsPjawe7Bv7x5
Co-authored-by: Claude <noreply@anthropic.com>1 parent eac28c3 commit 7bb8084
4 files changed
Lines changed: 134 additions & 3 deletions
File tree
- .changeset
- packages/client/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
309 | 309 | | |
310 | 310 | | |
311 | 311 | | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
312 | 339 | | |
313 | 340 | | |
314 | 341 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2045 | 2045 | | |
2046 | 2046 | | |
2047 | 2047 | | |
| 2048 | + | |
| 2049 | + | |
| 2050 | + | |
| 2051 | + | |
| 2052 | + | |
| 2053 | + | |
| 2054 | + | |
| 2055 | + | |
| 2056 | + | |
| 2057 | + | |
| 2058 | + | |
| 2059 | + | |
| 2060 | + | |
| 2061 | + | |
| 2062 | + | |
| 2063 | + | |
| 2064 | + | |
| 2065 | + | |
| 2066 | + | |
| 2067 | + | |
| 2068 | + | |
| 2069 | + | |
| 2070 | + | |
| 2071 | + | |
| 2072 | + | |
| 2073 | + | |
| 2074 | + | |
| 2075 | + | |
| 2076 | + | |
| 2077 | + | |
| 2078 | + | |
| 2079 | + | |
| 2080 | + | |
| 2081 | + | |
| 2082 | + | |
| 2083 | + | |
| 2084 | + | |
| 2085 | + | |
| 2086 | + | |
| 2087 | + | |
| 2088 | + | |
| 2089 | + | |
| 2090 | + | |
| 2091 | + | |
| 2092 | + | |
| 2093 | + | |
| 2094 | + | |
| 2095 | + | |
| 2096 | + | |
| 2097 | + | |
| 2098 | + | |
| 2099 | + | |
| 2100 | + | |
| 2101 | + | |
| 2102 | + | |
| 2103 | + | |
2048 | 2104 | | |
2049 | 2105 | | |
2050 | 2106 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
417 | 417 | | |
418 | 418 | | |
419 | 419 | | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
420 | 453 | | |
421 | 454 | | |
422 | 455 | | |
| |||
7056 | 7089 | | |
7057 | 7090 | | |
7058 | 7091 | | |
7059 | | - | |
| 7092 | + | |
| 7093 | + | |
7060 | 7094 | | |
7061 | | - | |
| 7095 | + | |
7062 | 7096 | | |
7063 | 7097 | | |
7064 | 7098 | | |
| |||
7944 | 7978 | | |
7945 | 7979 | | |
7946 | 7980 | | |
7947 | | - | |
| 7981 | + | |
7948 | 7982 | | |
7949 | 7983 | | |
7950 | 7984 | | |
| |||
0 commit comments