Skip to content

Commit 289bb43

Browse files
claude[bot]claude
andauthored
rest: the server-built findData literals speak the canonical QueryAST; retire wireDialectQuery (#16648)
* wip: canonical QueryAST rewrite + pin (in progress) * wip: fixture triage for canonical spelling * chore: re-anchor system-context census after line shift * chore: changeset * docs: cite #16581 for the picker filter-shape defect --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0f07b2c commit 289bb43

6 files changed

Lines changed: 556 additions & 65 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/rest": patch
3+
---
4+
5+
The REST server's own `findData` calls now build the canonical QueryAST instead of an undeclared wire dialect, and the helper that erased the type on that one slot is gone.
6+
7+
Four server-built query literals in `rest-server.ts` — the import-job loader, the import-job listing, the export chunk loop and the public reference picker — spelled their query in transport aliases (`$filter`, `$top`, `$skip`, `$orderby`, `$expand`, plus the bare `filters` / `select` / `sort`). None of those spellings is declared by `QuerySchema`, so three of them were routed through a `wireDialectQuery` helper that cast the `query` member to `FindDataRequest['query']`, and the fourth escaped the compiler entirely because its protocol handle was typed `any`. All four now spell `object` / `where` / `orderBy` / `limit` / `offset` / `fields` / `expand`, so the slot compiles against the declared contract like every other member of the request, and the helper is retired.
8+
9+
**No behaviour moves, and that is measured rather than asserted.** `@objectstack/metadata-protocol`'s `findData` folds every alias onto its canonical key by the spec's own table (`RPC_QUERY_ALIAS_SLOTS`) and moves the value verbatim, so both spellings reach `engine.find` as the same option bag. `rest-server-canonical-query-ast.test.ts` drives all four before/after pairs through the real normalizer and asserts that equality, and reads the source to keep the erasure retired — a cast compiles, so a type-check alone could not hold this ground.
10+
11+
**Nothing is removed from the published surface.** `wireDialectQuery` was a module-local `const` in `rest-server.ts`: it carried no `export` keyword, `packages/rest/src/index.ts` never named it, and it appeared in no other file in the tree. Deleting it moves no exported symbol, which is why this is a patch.
12+
13+
**What this change deliberately does NOT do:** it does not touch how the HTTP door treats a *caller's* query. The wire aliases stay accepted on `GET /data/:object` exactly as before — declaring them in the spec's alias table is a separate piece of work — and `GET /data/:object` still forwards the caller's own querystring bag untouched.

packages/rest/src/public-form-lookup-picker.test.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,16 +203,27 @@ describe('#7467 a spec-valid stored form carrying a publicPicker reaches the loo
203203
// the declared object override, the declared cap, the declared filter
204204
// rows ahead of the visitor's search predicate, id + displayFields
205205
// projection, offset pinned to 0 (no anonymous pagination).
206+
//
207+
// [#16337] The KEYS are the canonical QueryAST ones (`where` / `fields`
208+
// / `orderBy`); until then the route spelled them `filters` / `select` /
209+
// `sort`, wire aliases the normalizer folds onto exactly these. The
210+
// VALUES are byte-identical across that rewrite, which is the point —
211+
// and note what `where` carries: `ViewFilterRule` rows, the dialect
212+
// `FormFieldPublicPickerSchema.filter` declares, NOT a
213+
// `FilterCondition`. `findData` is stubbed in this suite, so it never
214+
// meets the ingress's verdict on that value; the real normalizer
215+
// refuses it (#16581) — ⛔ do not "repair" it by editing this
216+
// expectation.
206217
expect(findData).toHaveBeenCalledTimes(1);
207218
const call = findData.mock.calls[0][0];
208219
expect(call.object).toBe('sys_user');
209220
expect(call.query.limit).toBe(10);
210221
expect(call.query.offset).toBe(0);
211-
expect(call.query.select).toEqual(['id', 'name', 'email']);
222+
expect(call.query.fields).toEqual(['id', 'name', 'email']);
212223
// [#7485] Ordering is fixed, not authorable: first display field,
213224
// ascending. The route's `picker.sort ??` read is retired.
214-
expect(call.query.sort).toEqual([{ field: 'name', order: 'asc' }]);
215-
expect(call.query.filters).toEqual([
225+
expect(call.query.orderBy).toEqual([{ field: 'name', order: 'asc' }]);
226+
expect(call.query.where).toEqual([
216227
{ field: 'is_active', operator: 'equals', value: true },
217228
{ field: 'name', operator: 'contains', value: 'ad' },
218229
]);
@@ -299,7 +310,7 @@ describe('#7485 publicPicker.sort is retired — not declarable, and not read',
299310
// The stored `{ field: 'email', order: 'desc' }` reaches `findData`
300311
// nowhere: the fixed default is the only ordering the route composes.
301312
expect(findData).toHaveBeenCalledTimes(1);
302-
expect(findData.mock.calls[0][0].query.sort).toEqual([{ field: 'name', order: 'asc' }]);
313+
expect(findData.mock.calls[0][0].query.orderBy).toEqual([{ field: 'name', order: 'asc' }]);
303314
});
304315

305316
it('…and the fixed sort tracks displayFields[0], including the no-displayFields default', async () => {
@@ -310,15 +321,15 @@ describe('#7485 publicPicker.sort is retired — not declarable, and not read',
310321
const stored = await persistedBody(studioForm([{ field: 'owner', publicPicker: { object: 'sys_user' } }]));
311322
const { findData, lookup } = routesOver(stored, []);
312323
await lookup.handler({ params: { slug: 'contact', field: 'owner' }, query: {} } as any, mockRes());
313-
expect(findData.mock.calls[0][0].query.sort).toEqual([{ field: 'name', order: 'asc' }]);
324+
expect(findData.mock.calls[0][0].query.orderBy).toEqual([{ field: 'name', order: 'asc' }]);
314325

315326
const stored2 = await persistedBody(studioForm([{
316327
field: 'owner',
317328
publicPicker: { displayFields: ['email', 'name'], object: 'sys_user' },
318329
}]));
319330
const second = routesOver(stored2, []);
320331
await second.lookup.handler({ params: { slug: 'contact', field: 'owner' }, query: {} } as any, mockRes());
321-
expect(second.findData.mock.calls[0][0].query.sort).toEqual([{ field: 'email', order: 'asc' }]);
332+
expect(second.findData.mock.calls[0][0].query.orderBy).toEqual([{ field: 'email', order: 'asc' }]);
322333
});
323334
});
324335

0 commit comments

Comments
 (0)