rest: the server-built findData literals speak the canonical QueryAST; retire wireDialectQuery - #16648
Conversation
…nddata-canonical-queryast # Conflicts: # content/docs/permissions/system-context.mdx
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 86fd1912eb17672079aa406f2fe46e61bf93f7b0 && git checkout 86fd1912eb17672079aa406f2fe46e61bf93f7b0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bc0ac1d1f5775659e72ad35452c463443a71f1be c0ae12d76bc705f4a98d8233c78f6356ef43d5fc && git checkout -B drift-repro bc0ac1d1f5775659e72ad35452c463443a71f1be && git merge --no-ff c0ae12d76bc705f4a98d8233c78f6356ef43d5fc
node scripts/docs-audit/affected-docs.mjs --json bc0ac1d1f5775659e72ad35452c463443a71f1be |
Landing — green on
|
Fixes #16337
The three server-built
findDataliterals inpackages/rest/src/rest-server.tsnow speak the canonical QueryAST, andwireDialectQuery— the helper that cast thequeryslot so they could compile — is retired.What changed, site by site
GET /data/import/jobswireDialectQuery({ $filter: filter, $orderby: { created_at: 'desc' }, $top: limit, $skip: offset }){ object: IMPORT_JOB_OBJECT, where: filter, orderBy: [{ field: 'created_at', order: 'desc' }], limit, offset }GET /data/:object/exportwireDialectQuery({ $filter, $search, $searchFields, $orderby, $expand: names.join(','), $top: take, $skip: skip }){ object: objectName, where, search, searchFields, orderBy, expand: { REL: { object: REL } }, limit: take, offset: skip }GET /forms/:slug/lookup/:fieldwireDialectQuery({ limit, offset, filters, select, sort }){ object: referenceTo, limit, offset, where: filters, fields, orderBy }loadImportJob) — see below{ $filter: { id: jobId }, $top: 1 }, untyped{ object: IMPORT_JOB_OBJECT, where: { id: jobId }, limit: 1 }, annotated with the file'sServerScopedDataRequestenvelope overFindDataRequestA fourth literal, and why it is in this PR
The card names three sites. There is a fourth in the same file:
loadImportJobbuilt{ $filter, $top }and handed it to ap: anyprotocol handle, so it was type-checked by nothing at all and its wire spellings cost no diagnostic. It is the same defect class, the same mechanical rewrite, and it sits in the file this card's pin is keyed to.It is included because a guard written over the whole CLASS finds it and a guard that enumerates the three known instances does not. Leaving it would have forced the pin to carve out an exception for the one literal that most needed watching. The claim's file surface is unchanged — the repair is inside
rest-server.ts.The rewrite moves no behaviour, measured
@objectstack/metadata-protocol'sfindDatafolds every alias onto its canonical key by the spec's ownRPC_QUERY_ALIAS_SLOTSand moves the value verbatim, so both dialects should reachengine.findas the same option bag. That is a claim, so it was driven rather than asserted — every before/after pair through the realObjectStackProtocolImplementationover a recording engine:Equal in every pair. §3 of the new pin holds that equality permanently, with a CONTROL proving the instrument can tell two option bags apart.
wherecarriesViewFilterRulerows ({field, operator, value}), which is not aFilterCondition, so the ingress answers400 INVALID_FILTER— before this PR and after it alike. That defect is already filed as #16581 and is out of scope here: this PR retypes the spelling of these literals and moves no behaviour. #16581 remains open.The pin, and proof it can go red
packages/rest/src/rest-server-canonical-query-ast.test.ts(14 cases, 4 sections). §1 reads the SOURCE, because a type-check cannot hold this ground: the erasure it replaces was a cast, and a cast compiles. That is measured, not assumed — three ablation legs, each mutating the committed implementation, proving the mutation on disk by exact occurrence count and blob hash, then restoring under anEXIT/INT/TERMtrap and proving byte-identity against the HEAD blob:tsc --noEmitwhere: filter→where: filter as anyno server-built query literal carries an as castlimit,→top: limit,(topIS a declared key)no server-built query literal spells a wire alias+GET /data/import/jobs builds a canonical querywireDialectQueryreinstated and usedthe wireDialectQuery helper is goneEvery restore leg reported
RESTORED blob = 2bc1172e13a9a8a0a4f6679b85eda4b71bf01846(= HEAD) with an emptygit diff HEAD.tscstayed green on all three erasures, which is the argument for the pin existing at all.§2 is the type-level half: seven
@ts-expect-errordirectives asserting the declared contract refuses$top/filters/select/sort/ the record sort form / a comma-listexpand, and requiresobject. They are LIVE —packages/rest/tsconfig.test.jsoncompiles this layer and an unused directive is TS2578 there, socheck:test-typecheckpassing is what makes them assertions.wireDialectQuerywas NOT exported — measured four waysDeleting it moves no published face:
rest-server.ts:296carried noexportkeyword.packages/rest/src/index.ts— the package's only entry point, per itsexportsmap — never named it: 0 occurrences.rest-server.ts: 1 declaration, 3 calls, 1{@link}), so there was no re-export path.packages/rest/dist/index.d.ts/index.js/index.cjseach contain 0 occurrences.The changeset is graded
patchand states this explicitly rather than burying it.Verification
pnpm --filter @objectstack/rest test— 188 files / 3204 tests passed, on the tree merged withorigin/main.pnpm --filter @objectstack/rest typecheck— green, includingcheck:test-typecheck(test-layer debt ledger stays at zero entries).c0ae12d76b:node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, reconciled with--ran→ 57 derived, 57 run, 0 UNRUN.pnpm check:query-options-erasureis among them and passes.PREREQUISITE NOT MET, both needing a whole-repo build this container has not got:pnpm check:dual-build-cjs-loads(43 packages with nodist/) andpnpm check:type-check-debt(5 unbuilt workspace dependencies of the ledgered packages). Their siblings that do not need it —check:dts-closure,check:type-check-coverage— pass.Test-fixture triage
Five expectations across three suites pinned the door's OLD spelling and were re-spelled, never weakened:
query.$top→query.limit,query.$skip→query.offset,query.select→query.fields,query.sort→query.orderBy,query.filters→query.where, andquery.$expand === 'owner'→query.expanddeep-equals{ owner: { object: 'owner' } }(the map the normalizer lowered that comma list to). Every asserted VALUE is unchanged.验收备注
WIRE_QUERY_ALIAS_SLOTS/WIRE_DOLLAR_ALIASES, and declaring the transport aliases in the spec alias table). That is [finding]FindDataRequest.querydeclares the QueryAST, but the shippedfindDataingress also accepts an undeclared wire dialect — so that one slot cannot be compiled #16066's spec half, split from this card by the director ruling. Nothing in this diff touches it, andGET /data/:objectstill forwards the caller's own querystring bag untouched — the pin allowlists that one slot by name, with the reason.import-runner.tsstill builds three server-builtfindDataliterals in the undeclared wire dialect, through aquery: anyhelper #16638 —packages/rest/src/import-runner.tshas three more server-built wire-dialect literals, erased by afindArgsBase(query: any)helper. Same class, sibling file, outside this card's stated scope. [finding] rest:import-runner.tsstill builds three server-builtfindDataliterals in the undeclared wire dialect, through aquery: anyhelper #16638 is not addressed here.GET /forms/:slug/lookup/:fieldanswers 400 INVALID_FILTER for every search #16581 — the public picker's400 INVALID_FILTER. This PR's measurement is an independent confirmation of it.expandlowers each relation to a map entry whose key is the relation name and whose value is{ object: THAT_SAME_RELATION_NAME }— the nestedobjectnames the RELATION, not its target. That is the normalizer's own lowering, reproduced here byte-identically on purpose; whether it is the right lowering is a question about the normalizer, not about this door.FilterCondition's[key: string]: anyindex signature means an ARRAY still satisfies thewhereslot at compile time. It is why the picker's rule array compiles against a canonical key at all. Narrowing it lives inpackages/spec, which this card must not touch.Clause-② — re-derived from the delivered diff, still
nopackages/spec/src/**path in the diff (6 files, allpackages/rest/src/**plus one changeset).Generated by Claude Code