|
| 1 | +--- |
| 2 | +"@objectstack/spec": minor |
| 3 | +"@objectstack/metadata": patch |
| 4 | +--- |
| 5 | + |
| 6 | +fix(spec): `EngineQueryOptionsSchema.search` accepts the bare query string ADR-0061 D1 calls canonical (#7178) |
| 7 | + |
| 8 | +Two sibling schemas in `packages/spec` described the same key and disagreed. |
| 9 | +`BaseQuerySchema.search` (`query.zod.ts`, hence `QueryAST`, hence `DriverQuery`) |
| 10 | +has been `z.union([z.string(), FullTextSearchSchema])` since its own drift |
| 11 | +repair, with a doc comment saying why: the bare string **is** the canonical |
| 12 | +Tier-1 contract (ADR-0061 D1 — "the client sends only the query text; the server |
| 13 | +resolves which fields to search from object metadata"), it is what every surface |
| 14 | +sends, and it is what the dogfood HTTP proof pins. |
| 15 | +`EngineQueryOptionsSchema.search` — the options type of `IDataEngine.find` / |
| 16 | +`findOne` — declared the structured `FullTextSearchSchema` **only**. |
| 17 | + |
| 18 | +The runtime never agreed with that narrowing. `expandSearchOnAst` |
| 19 | +(`objectql/src/engine.ts`) reads `search` through `normalizeSearch`, whose first |
| 20 | +line is `if (typeof raw === 'string') return { query: raw }`, and |
| 21 | +`protocol-data.test.ts` asserts the protocol layer hands the engine a bare |
| 22 | +string. So the type forbade what the engine serves, and callers paid the |
| 23 | +standard price: `as any` on the query argument — which does not suppress |
| 24 | +`search` alone, it switches off checking for `where` / `orderBy` / `fields` in |
| 25 | +the same literal. Since this schema is not `.strict()`, an unknown key there is |
| 26 | +**silently dropped**, so the cast this divergence forced was precisely the cast |
| 27 | +`check:query-options-erasure` exists to stop. |
| 28 | + |
| 29 | +This is the same-family drift REPAIR, not a new dialect — the identical fix |
| 30 | +`BaseQuerySchema.search` already carries, for the identical reason. On the query |
| 31 | +side the divergence surfaced as a validation failure the moment #3899 started |
| 32 | +validating request bodies; here it surfaced as a type error, when #6231 retyped |
| 33 | +`DatabaseLoader`'s read helpers to `DriverQuery` and the **engine** branch alone |
| 34 | +refused to compile (TS2345 — `DriverQuery` not assignable to |
| 35 | +`EngineQueryOptionsParsed`, purely because of `search`; nothing else differs). |
| 36 | + |
| 37 | +Consumer census before landing, per the card's own guard: every site that reads |
| 38 | +object-form members off an engine-options `search` already narrows with `typeof` |
| 39 | +— `engine.ts` (`typeof raw === 'object' ? raw?.fields : undefined`), |
| 40 | +`search-filter.ts` `normalizeSearch`, and `metadata-protocol/protocol.ts`'s |
| 41 | +`searchFields` ingress gate. No consumer needed a guard added, and none changes |
| 42 | +behavior: they were all written for the union already. `count` is untouched — |
| 43 | +`EngineCountOptionsSchema` declares no `search` key at all. |
| 44 | + |
| 45 | +With the schemas agreed, the casts the divergence forced are deleted: |
| 46 | +`DatabaseLoader`'s three engine-branch `as any` (`_find` / `_findOne` / |
| 47 | +`_count`), which restores real `where` / `orderBy` / `fields` checking on the |
| 48 | +metadata main read path, and the seven `as any` in |
| 49 | +`engine-findone-contract.test.ts` that were passing the canonical spelling. |
| 50 | +`scripts/query-options-erasure-baseline.json` is ratcheted down accordingly. |
0 commit comments