Skip to content

[objectui] four live adapter.find calls pass an unprefixed query option — and no-query-params-under-options gates only the sibling half of the class #10470

Description

@os-zhuang

Filed, not fixed: the sites are in objectstack-ai/objectui, outside #10288's file
surface. Census taken at objectui c40f3b8ca21ddc19e05682f1719fcf16d4ce7fba
(objectstack pins 9a3daf8d37ad973a621e5edd276fe32467f90684; re-confirm at the pin
before acting).

The class

QueryParams (packages/types/src/data.ts) declares only $-prefixed keys, and
ObjectStackAdapter.convertQueryParams (packages/data-objectstack/src/index.ts)
builds its outgoing options by copying exactly those. Any other key reaches no branch
and is dropped — no throw, no warning. QueryParams also carries an index signature, so
the type system accepts both spellings equally.

The consequence is an unbounded read, not a truncated one: the platform's GET list
route has no default page size (objectstack packages/client/src/index.ts, pinned in
its client.test.ts), so an absent top returns the entire match set. A dropped cap is
therefore invisible until the object is large.

The four live sites

filecalldropped key
apps/console/src/sdui-workbench-preview.tsx:65find(objectName, { top: 200 })top
packages/app-shell/src/views/ObjectView.tsx:1561find(…, { limit: 0 })limit
packages/app-shell/src/views/metadata-admin/AssignedUsersSection.tsx:109find('sys_permission_set', { $filter: {…}, limit: 1 })limit
content/docs/guide/react-pages.md:129find(…, { filters: [['status','=','open']] })filters

ObjectView.tsx:1561's limit: 0 is worth reading twice: $top: 0 is honoured end to
end as "no records" (objectstack #6485 pinned it), so if that call means what it says,
dropping the key inverts it from no rows to every row.

AssignedUsersSection.tsx:109 is a one-line-away neighbour of three correct calls
($top: 500, $top: 200, $top: 1000 on L124/131/136) — the shape a reviewer's eye
slides over.

sdui-workbench-preview.tsx:66 also reads all.records off the result, with no .data
arm. find() resolves to a QueryResult (data, never records), so that preview
lists nothing regardless of the cap. skills/objectui/guides/data-integration.md:268
publishes the same return result.records; to skill consumers.

Why a rule, and which one

eslint-rules/no-query-params-under-options.js already gates the sibling shape —
find(obj, { options: { $top: 100 } }) — and its own header makes the argument for
mechanising this family: the mistake type-checks, it publishes, the symptom looks like a
data problem rather than a code problem, "so a review catches it once and then misses the
next one." It cites two live instances by two authors (object-timeline,
objectui#4009 / objectstack#7137; object-kanban, objectui#4025).

The rule bans a $-prefixed key under options. It does not look at a bare key at
the top level, which is the other half of the same class — and the half with four live
sites. Extending it (or adding a sibling rule) is the cheap move: the AST walk already
lands on the params object literal of a find/findOne call, the fix is a second
predicate, and the contract lives in the same repo as the rule.

Suggested shape, narrow on purpose: flag a static key that is a known query-option
name without its $ (top, skip, filter, filters, select, orderby, sort,
expand, search, count, limit, offset) in the second argument of an
adapter.find / dataSource.find / …findOne call. Not "any unprefixed key" — adapters
legitimately take adapter-specific params, which is why the index signature exists.

An objectstack-side gate was considered for #10288 and rejected: QueryParams is
objectui's contract, so a checker there would have to hard-code another repo's key list
and become a second source of truth for it. The rule belongs next to the type it
enforces.

Related

  • objectstack#10288 — the same two defects in examples/app-showcase, fixed there.
  • objectstack#10469 — the same .records misread in objectstack's own react-pages guide.
  • objectui#4734 / objectstack#7147 — the options: { $top } half, already gated.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions