/** * Opaque keyset-pagination cursor: a positional tuple encoding the sort-key of * the last returned row (e.g. `[block, index]` for an append-only feed, or * `[balance, address]` for a balance ranking). Pages are anchored to this * position rather than a numeric offset, so rows arriving at the head of a feed * cannot shift items across page boundaries (the duplicate/skip hazard of offset * pagination over a live feed). * * The token is opaque on purpose: callers must treat it as a blob and pass back * the `nextCursor` from the previous page verbatim. Because cursor values are * inlined into upstream SQL, every field is validated on decode against its * declared `Field` kind and re-serialized into a safe SQL literal via `literal`, * so a malformed or forged cursor degrades to "start from the head" rather than * reaching the query. */ export type Cursor = (string | number)[]; /** The kind of a cursor field, determining its validation and SQL literal form. */ export type Field = 'address' | 'id' | 'int' | 'timestamp' | 'uint'; /** Encodes a positional tuple as an opaque, URL-safe token. */ export declare function encode(values: Cursor): string; /** * Splits a keyset query result (fetched with one extra row, `LIMIT n + 1`) into * a page. Returns the page `rows` (capped at `limit`), whether `hasMore` rows * follow, and the `nextCursor` anchoring the next page below the last row. * * `key` derives the next-page cursor tuple from the last row of the page, * returning `undefined` when the row's sort-key can't be extracted (then * `nextCursor` is `null`). Callers build their typed `data` from `rows`. */ export declare function paginate(options: { /** The raw query result, fetched with one extra row (`LIMIT limit + 1`). */ rows: readonly row[]; /** The requested page size. */ limit: number; /** Derives the next-page cursor tuple from the last row of the page. */ key: (row: row) => Cursor | undefined; }): { hasMore: boolean; nextCursor: string | null; rows: row[]; }; /** * Decodes a cursor token, validating arity and each field against `fields`. * Returns `undefined` for anything malformed/forged so a bad cursor falls back * to the head page instead of erroring (or reaching SQL). */ export declare function decode(token: string, fields: readonly Field[]): Cursor | undefined; /** * Renders a (decode-validated) cursor value as a safe SQL literal: integers and * uint256 decimals are inlined bare; addresses are lowercased and quoted; * timestamps are quoted. Only ever call with values returned from `decode`. */ export declare function literal(value: string | number, field: Field): string; /** * Builds a strict lexicographic keyset predicate for the given ORDER BY columns * and direction. Columns carry pre-rendered `literal`s. * * When every column sorts the same way, this emits a row-value (tuple) * comparison, e.g. for `ORDER BY a DESC, b DESC` after `(va, vb)`: * `((a, b) < (va, vb))`. This single-predicate form is the only one the * indexer's signature-decoded queries accept (they reject the equivalent * `OR`-expansion), and standard Postgres / ClickHouse support it too. * * When directions are mixed (e.g. a ranking `ORDER BY balance DESC, addr ASC`), * a tuple comparison can't express it, so it falls back to the `OR` expansion: * `((a < va) OR (a = va AND b > vb))`. Mixed-direction keysets only run against * ClickHouse, which accepts the `OR` form. * * Multi-column keysets additionally AND a redundant non-strict bound on the * first column (`a <= va`): neither the tuple nor the `OR` form alone lets the * ClickHouse planner prune the primary-key range, so without the bare bound a * deep cursor page scans from the tip (see the Q1 notes in `activities.ts`). */ export declare function keyset(columns: readonly { name: string; literal: string; order: 'asc' | 'desc'; }[]): string; //# sourceMappingURL=Cursor.d.ts.map