import { FilterValues, FindResult, LogicalCondition, FilterCondition, OrderBySpec, WhereFilterOp } from "@rebasepro/types"; import { FindParams } from "./transport"; /** * The server's page size when the caller does not ask for one. * * Re-exported rather than redeclared. This was its own `= 20` — a third * constant of this name in the workspace, next to `@rebasepro/common`'s 200 and * the 50 the REST layer actually applies — and a local copy of a number that * belongs to another process is a number that goes stale silently. */ export { DEFAULT_LIST_LIMIT as DEFAULT_PAGE_SIZE } from "@rebasepro/types"; /** * Three-way compare with SQL's type coercion but not its collation. Returns * `undefined` when the two values are not ordered relative to each other, * which is how NULL propagates through a comparison. */ export declare function compareValues(a: unknown, b: unknown): number | undefined; /** Equality with the wire's type erasure allowed for, but never across NULL. */ export declare function looseEquals(a: unknown, b: unknown): boolean; /** Evaluate one canonical operator against one row value. */ export declare function matchesOperator(rowValue: unknown, op: WhereFilterOp, filterValue: unknown): boolean; /** Evaluate a `where` clause: every field, and every tuple on a field, AND-ed. */ export declare function matchesWhere(row: Record, where: FilterValues | undefined): boolean; /** Evaluate a nested and/or tree. */ export declare function matchesLogical(row: Record, condition: LogicalCondition | FilterCondition | undefined): boolean; /** * Approximate the server's full-text search with a case-insensitive substring * scan over the row's own string fields. Narrower than the real thing (no * stemming, no configured search columns), and it never matches a field the * cached row does not carry — a local list may therefore be missing rows the * server would have returned, which is why {@link isExactlyEvaluable} refuses * to call a search query exact. */ export declare function matchesSearch(row: Record, searchString: string | undefined): boolean; /** Does this row belong in the result set for `params`, ignoring pagination? */ export declare function matchesParams(row: Record, params?: FindParams): boolean; /** * Sort in place, Postgres-style: nulls last ascending, first descending, with * the row id as a tiebreak so paging through an unsorted-but-equal run does * not shuffle rows between pages. * * The tiebreak runs *descending*, which is not a taste: every server-side sort * ends on `id DESC` — `FetchService.buildOrderExpressions` appends it to make * the ordering total, and the keyset cursor is built to match. This ran * ascending, so two rows sharing a sort value came back from the local overlay * in the opposite order to the server's, and {@link isLocallySortable} called * that page exactly reproducible while it was not. */ export declare function sortRows>(rows: M[], orderBy?: OrderBySpec): M[]; /** * Resolve `page`/`offset`/`limit` the way the server does. * * It did not: this defaulted an absent limit to 20 while `/api/data` pages by * 50, so the same `observe()` answered with 20 rows from the local database and * 50 from the network — a list that changed length depending on which side * answered, with `page` striding differently on each. Delegated now, so the * sentence above is true by construction rather than by agreement. */ export declare function resolvePagination(params?: FindParams): { limit: number; offset: number; }; /** * Can a locally evaluated answer to `params` be trusted to match the server's, * assuming the cache holds every row of the collection? * * `include` pulls in rows from other collections that this evaluator never * sees, and `searchString` is only approximated — both make the local answer a * best effort rather than an equivalent one. * * **Ordering comparisons are refused, and that is the interesting one.** * `compareValues` falls back to an `Intl.Collator` for operands it cannot read * as numbers or instants. PostgreSQL orders text by the *database's* collation, * which is a property of the server this process has never been told: under the * C collation `'apple' < 'Banana'` is false, under `en_US.UTF-8` it is true, * and the collator says true. So `["<", "Banana"]` selects a different set here * than it does there — silently, and in whichever direction the deployment * happens to have been created. * * The refusal covers *every* ordering comparison rather than only the ones with * a string operand, because the operand type does not settle it: a numeric * bound against a text column (`["<", 10]` on a `varchar`) also reaches the * collator, and nothing in `params` says what the column holds. Conservative on * purpose — the cost is that a query combining an ordering filter with * *unsynced local writes* stops placing those writes optimistically, which is a * degraded answer rather than a wrong one. Claiming exactness we do not have is * the other way round. * * This says nothing about ordering *results*; that is a separate claim with a * separate answer, because a sort changes which rows come first and not which * rows match. See {@link isLocallySortable}. */ export declare function isExactlyEvaluable(params?: FindParams): boolean; /** * Would sorting `rows` locally reproduce the order the server would have sent? * * Asked of the rows rather than of the query, because unlike a filter this one * *is* decidable from the data in hand: {@link compareValues} reaches the * collator only when it cannot read both operands as numbers, and `toComparable` * has already turned dates and relations into numbers and ids by then. If every * value on the sort column normalises to a number, the collator is unreachable * and the local order is the server's order. * * A text column is therefore refused — see {@link isExactlyEvaluable} for why * the two cannot be made to agree — and so is a column this page happens to see * only as strings, which is the same thing from here. * * Nulls are fine either way: they are ordered by an explicit rule (last * ascending, first descending) that matches Postgres and never reaches the * comparator. */ export declare function isLocallySortable(rows: readonly Record[], orderBy?: OrderBySpec): boolean; /** Run a full query — filter, sort, paginate — over a set of rows. */ export declare function runLocalQuery>(rows: M[], params?: FindParams): FindResult;