/** * Compile-time assertions about the query surface. * * ## Read this before adding a `.test.ts` for a type * * These assertions are **not** in a test file, on purpose. In this repo a jest * test cannot check a type at all: * * - `ts-jest` is configured transpile-only. Verified: a test containing * `const n: number = "nope"` passes. `@ts-expect-error` in a `.test.ts` is * therefore inert — it asserts nothing and never fails. * - `tsconfig.typecheck.json` — the gate CI runs as `pnpm run typecheck` — * covers every package's `src` directory but **excludes every `*.test.ts`**. * * So a type assertion written as a test is checked by nothing, twice over. This * file is a plain module under `src`, which is exactly what the gate does read. * It is imported by nothing and emits no runtime code. * * ## What went wrong that this exists to prevent * * `_score` was accepted by the runtime, documented in the SDK docs and skills, * and rejected by `orderBy`'s type, which was `keyof M`. On a project with a * generated SDK — where `M` is a concrete row type — the documented call was a * compile error. Nothing in this repo noticed; a downstream application did. */ import type { FindParams, FindResult, SDKQueryBuilderInterface, WhereFilterOp } from "@rebasepro/types"; /** * A row shaped the way a **generated** SDK shapes one: a type alias with a * finite key set. * * This detail is the whole test. An `interface … extends Record` also satisfies the constraint, but its index signature makes * `keyof M` collapse to `string` — so every assertion below would pass no * matter what `orderBy` accepted, typos included. That is how the first draft * of this file was written, and every `@ts-expect-error` in it reported * "unused directive": the fixture proved nothing. * * A generated row type has no index signature, which is exactly why a real * project caught what this repo did not. */ type ContractRow = { id: string; title: string; created_at: string; /** An `array` property, which codegen emits as `Array`. */ tags: string[]; age: number; deleted_at: string | null; }; /** A to-many relation, which codegen emits as `Array`. */ type TagRow = { id: string; label: string; }; type PostRow = { id: string; title: string; tags: TagRow[]; }; /** The documented relevance sort must compile. */ export declare const orderByScore: FindParams; /** An ordinary column must keep compiling. */ export declare const orderByColumn: FindParams; /** * A column that does not exist must still be refused. Widening `orderBy` to * `string` would have fixed the `_score` error and silently given up this, * turning every typo into an unsorted 200 in production. */ export declare const orderByTypo: FindParams; export declare const fluentScore: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; export declare const fluentColumn: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; export declare const fluentTypo: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; /** Vector search must be reachable from the builder, and chain. */ export declare const fluentVector: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; /** * `array-contains` takes an **element** of the column, not the column. * * This was the second `_score`: documented in `docs/sdk/querying.md`, accepted * by the runtime, and a compile error on a generated SDK — because * `WhereValue = T | T[] | null` was one value type for all sixteen * operators, so on `tags: string[]` it wanted a `string[]`. The spelling that * did compile, `["featured"]`, builds `@> ARRAY[$1]` with the whole array bound * as the single element and matches nothing, forever, with no error anywhere. */ export declare const fluentArrayContains: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; export declare const fluentArrayContainsWrapped: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; /** * Same defect, and the case the relation compiler was specifically built for: * a to-many relation is emitted as `Array`, and the compiler answers * `array-contains` on it by comparing **ids**. So the id must be accepted even * though it is not the element type. */ export declare const fluentRelationContains: (qb: SDKQueryBuilderInterface, tagId: string) => SDKQueryBuilderInterface; export declare const fluentRelationIn: (qb: SDKQueryBuilderInterface, tagIds: string[]) => SDKQueryBuilderInterface; export declare const fluentRelationTypo: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; /** The list operators take a list of elements — or one, read as a one-element list. */ export declare const fluentInList: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; export declare const fluentInScalar: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; export declare const fluentInNested: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; /** A comparison takes one value. `eq(column, ["a","b"])` is not a query anyone meant. */ export declare const fluentEqArray: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; /** * A pattern is a string on every column type. The driver casts, so refusing * `"%3%"` on a numeric column was the type being stricter than the runtime. */ export declare const fluentLikeOnNumber: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; /** The null operators ignore their value; `null` is the conventional spelling. */ export declare const fluentIsNull: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; /** * A caller holding an unnarrowed operator — a dynamic filter UI — must keep * compiling. `WhereValueFor` distributes over the operator, so this is the * union of every branch rather than a `never`. */ export declare const fluentDynamicOp: (qb: SDKQueryBuilderInterface, op: WhereFilterOp) => SDKQueryBuilderInterface; /** Two conditions on one column: the shape the Mongo compiler used to drop. */ export declare const fluentRange: (qb: SDKQueryBuilderInterface) => SDKQueryBuilderInterface; /** The object form must accept exactly what the fluent form accepts. */ export declare const paramsArrayContains: FindParams; /** …and the array-of-tuples form the builder produces from two `.where()` calls. */ export declare const paramsRange: FindParams; /** * Sorting by relevance and then being unable to read it was the other half of * the same bug — the e2e cast around it, which should have been the tell. */ export declare const readComputed: (result: FindResult) => { score: number | undefined; distance: number | undefined; title: string; }; /** Widening the row must not have turned it into `any`. */ export declare const readUnknown: (result: FindResult) => any; /** * A result row must stay assignable to `Record`. * * Widening the row to `M & QueryComputedFields` broke this in seven places in * one downstream app, because `QueryComputedFields` was first written as an * `interface`: TypeScript grants an implicit index signature to a type alias * and withholds it from an interface, so the intersection stopped overlapping * with `Record` and every `as Record` cast * became an error. Nothing in this repo casts a row that way, which is why * nothing here noticed. */ export declare const rowStaysIndexable: (result: FindResult) => Record[]; export {};