Skip to content

Commit 4e057d9

Browse files
committed
chore: changeset for the counted search total; soft page assertions in the door pin
Claude-Session: https://claude.ai/code/session_01JfJfBUC3cQ6hhgm9MQK76T Co-authored-by: Claude <noreply@anthropic.com>
1 parent e85d2a4 commit 4e057d9

2 files changed

Lines changed: 22 additions & 4 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/objectql": minor
4+
"@objectstack/metadata-protocol": patch
5+
---
6+
7+
A paged list under a search now reports the real number of matching records as `total`, and `hasMore` agrees with it on every page.
8+
9+
Clause-②: yes (widening) — `EngineCountOptionsSchema` gains two OPTIONAL keys, `search` and `searchFields`, so the accept set of the count options grows. Nothing previously admitted is refused, no key is renamed or retired, and no producer is required to write them.
10+
11+
Until now `GET /api/v1/data/:object?$top=25&$search=Line` over 75 matching records answered `total: 26` (offset + page + 1, a page-local estimate), and the last page claimed `hasMore: true`; `POST /api/v1/data/:object/query` with a `search` did the same. Nothing on the response marked the number as an estimate, so the Console rendered it as the record count and offered the wrong number of pages.
12+
13+
- **`@objectstack/spec`** — `EngineCountOptionsSchema` declares `search` (the bare string, or the structured `FullTextSearchSchema` form) and `searchFields`, identically to `EngineQueryOptionsSchema`. FROM `count(object, { where })` only → TO `count(object, { where, search, searchFields })`. The structured `search` form carries flag defaults, so the parsed state is named `EngineCountOptionsParsed` (ADR-0122). `HookCountQuery` (`ctx.api.object(name).count()`) derives from it and gains the same two keys.
14+
- **`@objectstack/objectql`** — `engine.count()` (and `ctx.api.object(name).count()`) accepts the two keys it used to refuse as unknown options, and expands them through the same ADR-0061 expansion `find()` and `aggregate()` use: the same server-resolved searchable fields, the same `internal: true` exclusion, the same `searchFields` narrowing, AND-ed with `where` before the security middlewares run. There is one expander, not two, so a count under a search is the number of rows `find()` returns for the same bag.
15+
- **`@objectstack/metadata-protocol`** — `findData`'s paged branch passes `search` / `searchFields` to `engine.count()`, and the page-local estimate branch is gone. `$count=false` still skips the count and omits `total`.
16+
17+
Nothing to migrate. A client that worked around the estimate, for example by labelling the count as page-local under a search, can read `total` as the match count again.

‎packages/rest/src/list-search-total-door.test.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,14 @@ describe('[#22790] a paged search reports the counted total', () => {
115115
const unpaged = await list({ $search: 'Line' });
116116
expect(unpaged.records).toHaveLength(75);
117117

118+
// Soft, so a red run names every wrong number — the first page AND the last.
118119
const first = await list({ $top: '25', $search: 'Line' });
119-
expect(first).toMatchObject({ total: 75, hasMore: true });
120-
expect(first.records).toHaveLength(25);
120+
expect.soft(first).toMatchObject({ total: 75, hasMore: true });
121+
expect.soft(first.records).toHaveLength(25);
121122

122123
const last = await list({ $top: '25', $skip: '50', $search: 'Line' });
123-
expect(last).toMatchObject({ total: 75, hasMore: false });
124-
expect(last.records).toHaveLength(25);
124+
expect.soft(last).toMatchObject({ total: 75, hasMore: false });
125+
expect.soft(last.records).toHaveLength(25);
125126
});
126127

127128
it('POST …/query: the same, and a `searchFields` narrowing narrows the count with the rows', async () => {

0 commit comments

Comments
 (0)