/** * Compose, merge, join, and paginate index queries as Effect streams. * * A query stream is an Effect `Stream` of decoded documents with stored keys, a * shared key layout, and a direction. Create one with * `reader.table(...).stream(index, range?, order?)` for a standard index. The * order defaults to `"asc"`; search indexes use `reader.table(...).search(...)` * instead. Unlike `reader.table(...).index(...).stream()`, this returns a * composable query stream rather than a plain `Stream` over one query. * * Creating or composing a stream does not read documents. Reads begin when you * run a consuming effect, such as `Stream.runCollect` or `paginate`, and each * run executes the queries again. Keep callbacks deterministic and read-only: * pagination, seeks, and reversal can reevaluate them. * * Key values are stored separately from emitted values. Pinning an index field * with `eq` removes it from the key; range bounds keep it. Keys retain creation * time and document-ID tiebreakers where applicable. `QueryStream` combinators * preserve the ordering information needed to merge and paginate; plain * `Stream` transforms return ordinary streams without that information. A key * layout describes the key positions, their visible labels, and implicit ID * positions. Reuse a stream's `keyLayout` with `empty` or `flatMap`. * * Return the whole `paginate` result from a paginated query handler. In React, * use `useStreamPaginatedQuery`, not `usePaginatedQuery`: stream pages aren't * tracked by Convex's query journal, so the stream hook pins and splits page * ranges to keep loaded pages gap-free. Foldkit's `PaginatedQuery` also * supports stream pagination. * * Stream querying is experimental. Its API may still change. */ import type { PaginationOptions as ConvexPaginationOptions, PaginationResult as ConvexPaginationResult } from "convex/server"; import { type Pipeable } from "effect/Pipeable"; import * as Array from "effect/Array"; import type * as Channel from "effect/Channel"; import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import type * as Types from "effect/Types"; import * as Document from "./Document"; import type * as Table from "./Table"; import * as QueryStreamKeyLabels from "./QueryStreamKeyLabels"; import * as QueryStreamKeyLayout from "./QueryStreamKeyLayout"; import * as QueryStreamOrderDirection from "./QueryStreamOrderDirection"; import * as QueryStreamKeyValues from "./QueryStreamKeyValues"; import * as QueryStreamKeyBounds from "./QueryStreamKeyBounds"; import * as QueryStreamIndexRange from "./QueryStreamIndexRange"; import * as QueryStreamReadBudget from "./QueryStreamReadBudget"; declare const ReadBudgetExceededError_base: Schema.Class, import("effect/Cause").YieldableError>; /** * A page exhausted its read budget without a safe logical continuation. * * @experimental */ export declare class ReadBudgetExceededError extends ReadBudgetExceededError_base { get message(): string; } declare const IncompatibleStreamsError_base: new = {}>(args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { readonly _tag: "IncompatibleStreamsError"; } & Readonly; /** * Thrown by `merge` when inputs have different directions, visible ordering * labels, or implicit-ID positions. The payload identifies the expected and * actual direction and layout; map and relabel compatible inputs before * merging. * * @experimental */ export declare class IncompatibleStreamsError extends IncompatibleStreamsError_base<{ readonly expectedOrderDirection: QueryStreamOrderDirection.QueryStreamOrderDirection; readonly actualOrderDirection: QueryStreamOrderDirection.QueryStreamOrderDirection; readonly expectedKeyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout; readonly actualKeyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout; }> { get message(): string; } declare const InnerStreamOrderMismatchError_base: new = {}>(args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { readonly _tag: "InnerStreamOrderMismatchError"; } & Readonly; /** * A `flatMap` inner stream runs in a different direction from its outer stream. * The join dies with this error when it runs; `expectedOrderDirection` is the * outer direction and `actualOrderDirection` is the inner direction. * * @experimental */ export declare class InnerStreamOrderMismatchError extends InnerStreamOrderMismatchError_base<{ readonly expectedOrderDirection: QueryStreamOrderDirection.QueryStreamOrderDirection; readonly actualOrderDirection: QueryStreamOrderDirection.QueryStreamOrderDirection; }> { get message(): string; } declare const InnerStreamLayoutMismatchError_base: new = {}>(args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { readonly _tag: "InnerStreamLayoutMismatchError"; } & Readonly; /** * A `flatMap` inner stream does not match the supplied `innerKeyLayout`, * including visible labels and implicit-ID positions. The join dies with this * error when it runs; the payload contains the expected and actual layouts. * * @experimental */ export declare class InnerStreamLayoutMismatchError extends InnerStreamLayoutMismatchError_base<{ readonly expectedKeyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout; readonly actualKeyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout; }> { get message(): string; } declare const MissingReversalRecipeError_base: new = {}>(args: Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { readonly _tag: "MissingReversalRecipeError"; } & Readonly; /** * Thrown by `reverse` when a manually constructed stream has no `reverseWith` * recipe. Streams created through the table reader and `QueryStream` * combinators support reversal. * * @experimental */ export declare class MissingReversalRecipeError extends MissingReversalRecipeError_base { get message(): string; } /** * `paginate` dies with this error for `numItems: 0` and `cursor: null`, because * an empty initial page cannot establish a continuation boundary. Request a * positive number of items for the first page. * * @experimental */ export { EmptyInitialPageError, InvalidPageSizeError, } from "./QueryStreamPagination"; /** * Invalid numeric read limits. * * @experimental */ export { InvalidReadLimitError } from "./QueryStreamReadBudget"; /** * Runtime identifier used to distinguish query streams from plain streams. * * @experimental */ export declare const TypeId = "~@confect/server/QueryStream"; /** * Type of the query stream's runtime identifier. * * @experimental */ export type TypeId = typeof TypeId; /** * A decoded value and its stored key. `doc: None` marks an element that was * read but filtered out: it emits no value, but still advances cursors. Mapping * a value does not recompute its key. * * @experimental */ export declare class Element extends Data.Class<{ readonly doc: Option.Option; readonly keyValues: QueryStreamKeyValues.QueryStreamKeyValues; }> { } /** * An Effect `Stream` whose stored keys determine its order and pagination * boundaries independently of its emitted values. * * TypeScript normally infers all five parameters from the table's `stream` * method and subsequent composition: * * - `Doc` is the emitted value: a decoded document or a mapped or joined result. * - `KeyLabels` is the branded readonly tuple of visible ordering names, * initially the index fields not pinned by `eq`. `flatMap` appends labels and * `renameKey` replaces them. Implicit ID tiebreakers are omitted from labels * but retained in `keyLayout`. * - `OrderDirection` is `"asc"` or `"desc"`; the type defaults to their union. * - `E` contains typed failures while reading or transforming values. `never` * means no typed failures, not no defects. * - `R` contains Effect services required to run the stream. The stream retains * the database access supplied at creation, so it doesn't require providing * `DatabaseReader` again; effectful callbacks can add requirements. * * Known label or direction mismatches prevent merging at compile time. Runtime * checks also compare implicit-ID positions. Reuse a compatible stream's * `keyLayout` for `empty` or `flatMap`'s `innerKeyLayout`. * * Consume with Effect's `Stream.runCollect` or `Stream.runHead`, or use * `unique` or `paginate`. A plain `Stream` transform such as `Stream.take` * returns an ordinary stream that can be consumed but no longer merged or * paginated with this module. * * @experimental */ export declare class QueryStream implements Stream.Stream { readonly orderDirection: OrderDirection; /** * Shared description of the key positions, including visible labels and * implicit IDs. Reuse with `empty` or `flatMap`'s `innerKeyLayout`. Reading * this property does not read documents. Direction is in `orderDirection`. */ readonly keyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout; /** * Values paired with stored keys, including filtered markers that advance * cursors without emitting a value. */ readonly annotated: Stream.Stream, E, R | QueryStreamReadBudget.QueryStreamReadBudget>; /** * Query recipe retained by direct index streams and rebuilt on each run. * Includes the effective index bounds. Composed streams instead delegate * range changes to their inputs through `narrowWith`. */ readonly reflection?: Reflection | undefined; /** * Rebuilds the stream with validated key bounds, pushing them into index * ranges where that preserves the composition's results. Without this * recipe, `narrow` filters the annotated stream in memory. The constructor * guards recipe calls against bounds from incompatible layouts. */ readonly narrowWith?: ((parsedBounds: QueryStreamKeyBounds.ParsedBounds) => QueryStream) | undefined; /** * Rebuilds the stream in the opposite direction using index scans and * seeks. Distinct streams keep their original representatives. Without this * recipe, `reverse` throws `MissingReversalRecipeError`. */ readonly reverseWith?: (() => QueryStream, E, R>) | undefined; readonly [TypeId]: TypeId; readonly "~labels": Types.Invariant; readonly "~direction": Types.Covariant; readonly [Stream.TypeId]: Stream.VarianceStruct; readonly pipe: Pipeable["pipe"]; readonly channel: Channel.Channel, E, void, unknown, unknown, unknown, R>; constructor(orderDirection: OrderDirection, /** * Shared description of the key positions, including visible labels and * implicit IDs. Reuse with `empty` or `flatMap`'s `innerKeyLayout`. Reading * this property does not read documents. Direction is in `orderDirection`. */ keyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout, /** * Values paired with stored keys, including filtered markers that advance * cursors without emitting a value. */ annotated: Stream.Stream, E, R | QueryStreamReadBudget.QueryStreamReadBudget>, /** * Query recipe retained by direct index streams and rebuilt on each run. * Includes the effective index bounds. Composed streams instead delegate * range changes to their inputs through `narrowWith`. */ reflection?: Reflection | undefined, /** * Rebuilds the stream with validated key bounds, pushing them into index * ranges where that preserves the composition's results. Without this * recipe, `narrow` filters the annotated stream in memory. The constructor * guards recipe calls against bounds from incompatible layouts. */ narrowWith?: ((parsedBounds: QueryStreamKeyBounds.ParsedBounds) => QueryStream) | undefined, /** * Rebuilds the stream in the opposite direction using index scans and * seeks. Distinct streams keep their original representatives. Without this * recipe, `reverse` throws `MissingReversalRecipeError`. */ reverseWith?: (() => QueryStream, E, R>) | undefined); toStream(): Stream.Stream; } /** * A query stream with any value, ordering, error, and requirement types. * * @experimental */ export type Any = QueryStream; /** * Check whether a value retains query-stream ordering and pagination support. * Plain `Stream` transforms return ordinary streams, for which this is false. * * @experimental */ export declare const isQueryStream: (u: unknown) => u is Any; /** * Create a stream with no documents but a known key layout and direction. Use * it when a dynamic list of streams is empty, since `merge` requires at least * one input. The direction defaults to `"asc"`. * * Supply the document type in the first call, then reuse a compatible stream's * layout: `QueryStream.empty()(source.keyLayout, "desc")`. Creating * the source solely to obtain its layout does not read documents. * * @experimental */ export declare const empty: () => { (keyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout): QueryStream; (keyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout, orderDirection: OrderDirection): QueryStream; }; /** * Database access needed to rebuild a direct index query on each run. Normally * supplied by `reader.table(...).stream(...)` rather than implemented * manually. * * @experimental */ export interface ReflectionReader { query(tableName: string): { withIndex(indexName: string, indexRange?: (q: any) => any): { order(orderDirection: QueryStreamOrderDirection.QueryStreamOrderDirection): AsyncIterable; }; }; } /** * A reusable index-query description: database access, table schema, index, * range, and direction. Direct index streams retain this description instead of * a one-shot Convex query so each run can execute the query again. Narrowing * and pagination use it to rebuild queries with tighter index bounds. * * @experimental */ export interface Reflection { readonly reader: ReflectionReader; readonly table: Table.AnyWithProps; readonly indexName: string; /** * Index fields in declared order, including `_creationTime` where applicable. * The `by_id` index uses only `["_id"]`. */ readonly indexFieldPaths: ReadonlyArray; /** * Index constraints. Equality constraints pin leading fields, removing them * from the stream's key; range bounds do not. */ readonly indexRange: QueryStreamIndexRange.QueryStreamIndexRange; readonly orderDirection: OrderDirection; /** * Effective bounds in full index-key values, including pinned fields and ID * tiebreakers. If absent, bounds come from `indexRange`; supplied bounds * intersect those constraints rather than replacing them. */ readonly indexBounds?: QueryStreamKeyBounds.IndexBounds; } /** * Create a reusable query stream from an index-query description. Prefer * `reader.table(...).stream(...)` for inferred document and ordering types. * * Construction does not read documents. Each consuming run rebuilds the index * queries within the supplied range and decodes their documents, failing with * `DocumentDecodeError` if decoding fails. Key values come from encoded index * values before decoding, with equality-pinned fields removed. * * @experimental */ export declare const fromReflection: (reflection: Reflection) => QueryStream; /** * Combine streams sharing a key layout into one stream in key order. Use this * to query several index ranges at once, such as notes by multiple roles. * * The merge emits the smallest next key when ascending and the largest when * descending. Unlike `Stream.merge`, it does not interleave by arrival time. * Overlapping inputs are not deduplicated: a document matched twice appears * twice. Supply at least one input; use `empty` for an empty dynamic list. * * Inputs need compatible document types and identical key layouts and * directions. TypeScript rejects known mismatches; runtime mismatches throw * `IncompatibleStreamsError` when combined. Map to a common value shape and use * `renameKey` when compatible indexes have different field names. * * @experimental */ export declare const merge: (streams: readonly [QueryStream, ...ReadonlyArray, E, R>>]) => QueryStream; /** * Control concurrent callback evaluation in `filterEffect` and `mapEffect`. * * @experimental */ export interface EffectOptions { /** * Maximum number of document effects running at once, or `"unbounded"`. * Defaults to one at a time. Results retain stream order regardless of * completion order; callbacks must remain deterministic and read-only. */ readonly concurrency?: number | "unbounded" | undefined; } /** * Keep values that satisfy a pure predicate without changing their stored keys. * Like `Stream.filter`, this removes emitted values, but it retains the * ordering information needed to merge and paginate the result. * * Rejected documents still count as reads and advance cursors. Filtering before * `distinct` selects each group's first matching document; filtering after it * tests only the chosen representative. Use `filterEffect` for predicates that * read another table, use a service, or fail with a typed error. * * @experimental */ export declare const filter: ((predicate: (doc: Doc) => boolean) => (self: QueryStream) => QueryStream) & ((self: QueryStream, predicate: (doc: Doc) => boolean) => QueryStream); /** * Keep values that satisfy an effectful predicate, preserving stored keys and * cursor progress just like `filter`. The predicate can read other tables or * use services; its errors and requirements become part of the stream's types. * * Effects run one document at a time by default. Pass `{ concurrency }` to run * several at once while retaining stream order. Keep the predicate * deterministic and read-only, since seeks and pagination can run it again. * Separate lookups inside the predicate are not counted by pagination's read * budgets. * * @experimental */ export declare const filterEffect: ((predicate: (doc: Doc) => Effect.Effect, options?: EffectOptions) => (self: QueryStream) => QueryStream) & ((self: QueryStream, predicate: (doc: Doc) => Effect.Effect, options?: EffectOptions) => QueryStream); /** * Transform emitted values with a pure function while keeping their stored key * values. The result retains merge and pagination support, unlike the plain * stream returned by `Stream.map`. * * The mapper may replace any fields, including indexed fields, or return a * different shape entirely. It does not recompute keys or sort by the new * values: mapping note text to its length still orders by the original text, * creation time, and ID, not by length. Preserve the input value rather than * mutating it. Use `mapEffect` when the mapper needs an effect. * * @experimental */ export declare const map: ((f: (doc: Doc) => Doc2) => (self: QueryStream) => QueryStream) & ((self: QueryStream, f: (doc: Doc) => Doc2) => QueryStream); /** * Transform emitted values with an effect, preserving the original stored keys * just like `map`. The mapper may read another table or use services; its * errors and requirements become part of the stream's types. * * Effects run one document at a time by default. Pass `{ concurrency }` for * concurrent reads without changing output order. Keep the mapper deterministic * and read-only: seeks, reversal, and pagination can reevaluate it. Separate * lookups inside the mapper are not counted by pagination's read budgets. * * @experimental */ export declare const mapEffect: ((f: (doc: Doc) => Effect.Effect, options?: EffectOptions) => (self: QueryStream) => QueryStream) & ((self: QueryStream, f: (doc: Doc) => Effect.Effect, options?: EffectOptions) => QueryStream); /** * Run an inner query stream for each outer document and concatenate the * results. Like sequential `Stream.flatMap`, each inner stream finishes before * the next begins. The joined key is the outer key followed by the inner key, * retaining both document IDs so equal timestamps remain distinguishable. * * Supply `options.innerKeyLayout` from a compatible stream's `keyLayout`. For * example, comments indexed by `noteId` and pinned to a note have the same * layout as comments ordered by creation time: one visible creation-time * position followed by an implicit ID. Creating the template reads no * documents. For nested joins or renamed streams, apply the same composition or * renaming to the template before reusing its layout. * * The layout is required when constructing the joined stream, before any outer * document is available to pass to `f`, including when the outer stream is * empty. It determines the joined key positions and the width of empty-inner * markers. It contains no query results or direction. * * Every inner stream must match that layout and the outer direction. TypeScript * rejects known mismatches; others die with `InnerStreamOrderMismatchError` or * `InnerStreamLayoutMismatchError` when the join runs. * * An empty inner stream emits no value by default, but leaves a filtered marker * that advances cursors past its outer document. Pass `options.onEmpty` to emit * `onEmpty(outer)` instead. The element type widens to include the placeholder, * so map inner values to a common shape when needed. Its key is the outer key * followed by null inner components, and pagination steps past it like any * element. Outer documents filtered out before the join remain absent. * * Keep `f` and `onEmpty` deterministic and read-only. Pagination, narrowing, * and reversal can reevaluate them; both outer and inner index reads count * toward pagination's budgets. * * @experimental */ export declare const flatMap: ((f: (doc: Doc) => QueryStream, options: { readonly innerKeyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout>; readonly onEmpty?: ((doc: Doc) => Doc3) | undefined; }) => (self: QueryStream) => QueryStream, OrderDirection, E | E2, R | R2>) & ((self: QueryStream, f: (doc: Doc) => QueryStream, E2, R2>, options: { readonly innerKeyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout>; readonly onEmpty?: ((doc: Doc) => Doc3) | undefined; }) => QueryStream, OrderDirection, E | E2, R | R2>); /** * Keep the first document for each distinct value of a key prefix. Similar to * `Stream.changes` on that prefix, this groups consecutive equal keys, but * seeks past the rest of each group instead of scanning it. * * Pass a prefix of the visible ordering labels, such as `["text"]` for notes * ordered by text and creation time. Prefix validity is checked by TypeScript * and at runtime. On a `flatMap` result, a prefix reaching into the inner key * also groups by the outer document, including its ID. * * Operation order controls representative selection. Filter before `distinct` * to choose the first matching document, or after it to test only the selected * representative. `reverse(distinct(q))` keeps those representatives and flips * their output order; `distinct(reverse(q))` selects from the other direction. * * Narrowing after `distinct` filters the original representatives without * selecting replacements. Narrowing before it constrains the input and can * change the representatives. Preserving representatives may require extra * reads outside output bounds and repeated callback evaluation. * * @experimental */ export declare const distinct: (>(prefixNames: PrefixNames) => ]>, E, R, OrderDirection extends QueryStreamOrderDirection.QueryStreamOrderDirection>(self: QueryStream) => QueryStream) & (, Doc, KeyLabels extends QueryStreamKeyLabels.QueryStreamKeyLabels]>, E, R, OrderDirection extends QueryStreamOrderDirection.QueryStreamOrderDirection>(self: QueryStream, prefixNames: PrefixNames) => QueryStream); /** * Relabel visible key positions without changing values or their order. This * does not sort or transform emitted documents. * * Use it to merge indexes or tables ordered by comparable values under * different field names. For example, relabel comments ordered by `["body", * "_creationTime"]` as `["text", "_creationTime"]` to merge with notes ordered * by text. Map both streams to a common value shape first. * * Supply exactly as many replacement labels as the original visible labels. The * values at corresponding positions must be comparable; relabeling does not * establish that semantic compatibility for you. Implicit ID tiebreakers keep * their positions. For joined streams, replacement labels cover the combined * key, outer labels first. * * @experimental */ export declare const renameKey: (>(replacementNames: ReplacementNames) => (self: QueryStream) => QueryStream, OrderDirection, E, R>) & (, Doc, KeyLabels extends QueryStreamKeyLabels.QueryStreamKeyLabels & { readonly length: ReplacementNames["length"]; }, E, R, OrderDirection extends QueryStreamOrderDirection.QueryStreamOrderDirection>(self: QueryStream, replacementNames: ReplacementNames) => QueryStream, OrderDirection, E, R>); /** * Run a composed query stream in the opposite direction without changing its * key layout. Use this for bidirectional pagination: a reversed feed can load * its earlier pages. * * Reversal uses index scans and seeks rather than collecting and reversing * values. Merges, filters, maps, flat-maps (including their inner streams), * distinct streams, renamed streams, and empty streams all support it. * * `reverse(distinct(q))` retains each group's original first representative and * reverses their output order. `distinct(reverse(q))` instead chooses the first * document from the other direction. Preserving representatives can require * extra reads to discover groups and seek their original first documents. * * A manually constructed stream without `reverseWith` throws * `MissingReversalRecipeError`. * * @experimental */ export declare const reverse: (self: QueryStream) => QueryStream, E, R>; /** * Restrict a stream to keys between `start` and `end`. Provide at least one * endpoint, each with `keyValues` and a required `inclusive` flag; omit the * other endpoint to leave that side unbounded. * * Endpoints follow stream order: `start` is the lower key when ascending and * the upper key when descending. Repeated narrowing intersects existing bounds * and can only restrict the range further. * * A key may be a prefix of the full key. An inclusive prefix includes every key * extending it; an exclusive prefix excludes the whole group. For example, * creation-time bounds can use `[startTime]` and `[endTime]` without trailing * IDs. Equal endpoints produce an empty range unless both are inclusive, in * which case they select that key or prefix group. * * Bounds are pushed into index ranges where that preserves the composition's * results. On a distinct stream, full-key bounds filter original * representatives without choosing replacements; prefix bounds include or * exclude whole groups. This can require reads outside the output bounds. Apply * bounds before `distinct` instead to constrain which documents can represent a * group. Manually constructed streams without `narrowWith` are filtered in * memory. * * For pagination, pass the previous `continueCursor` unchanged to `paginate`; * do not decode a cursor into explicit bounds. * * @experimental */ export declare const narrow: ((narrowBounds: QueryStreamKeyBounds.NarrowBounds) => (self: QueryStream) => QueryStream) & ((self: QueryStream, narrowBounds: QueryStreamKeyBounds.NarrowBounds) => QueryStream); declare const NotUniqueError_base: Schema.Class, import("effect/Cause").YieldableError>; /** * `unique` found at least two emitted values. Narrow the query if at most one * value was expected, or use another consumer to handle multiple results. * * @experimental */ export declare class NotUniqueError extends NotUniqueError_base { get message(): string; } /** * Consume a stream expected to emit at most one value. The effect returns * `None` for no values and `Some(value)` for one, or fails with * `NotUniqueError` for two or more. Like `Stream.runHead` with a uniqueness * check, it inspects at most two emitted values; finding them may read * additional filtered documents. * * @experimental */ export declare const unique: (self: QueryStream) => Effect.Effect, E | NotUniqueError, R>; /** * Convex pagination options interpreted over a query stream's stored keys. Pass * a paginated query handler's `paginationOpts` directly to `paginate`. * * - `cursor`: required exclusive start boundary; `null` starts at the beginning. * - `numItems`: requested emitted values, excluding filtered documents. With an * `endCursor`, it is not an item limit, but still informs split * recommendations. * - `endCursor`: optional inclusive boundary returned by a previous page. Pins * the page to a key range regardless of how many values it holds. * - `maximumRowsRead`: optional budget for physical document reads from * underlying query-stream indexes, including filtered documents. * - `maximumBytesRead`: optional budget charging estimated document size on every * read, not Convex's exact billed bytes. The final document can take the * total over the limit because its size is known only after reading it. * * With `numItems: 0`, an existing cursor is returned unchanged with an empty * page and `isDone: false`; a null cursor dies with `EmptyInitialPageError`. * * @experimental */ export type PaginateOptions = ConvexPaginationOptions; /** * One page in Convex's pagination result shape. Return the whole result to * reactive clients so they can pin and split pages; use `page` alone for a * one-shot read inside a handler. * * - `page`: emitted values, possibly empty despite progress past filtered keys. * - `continueCursor`: the next page's exclusive start. Pass it back unchanged as * `cursor` while `isDone` is false. * - `splitCursor`: an optional interior boundary. Pin the left page with this as * `endCursor`; start the right page here, retaining the original end. * - `pageStatus`: `"SplitRequired"` when a read budget stops at a safe boundary, * or `"SplitRecommended"` when a pinned page grows too large or a page scans * many keys. Both include a `splitCursor`; otherwise status may be absent. * - `isDone`: true confirms the stream's end. Reaching a pinned boundary does not * prove completion. A split recommendation can report false even at the end, * so handle splitting before requesting another page. * * @experimental */ export type PaginationResult = ConvexPaginationResult; /** * Return an effect producing one page of a composed query stream. Start with * `cursor: null`, then pass each returned `continueCursor` back unchanged until * `isDone`. Cursors record key boundaries, not offsets or the last visible * document: filtered elements can advance the cursor without entering `page`. * * `cursor` is exclusive and `endCursor` is inclusive, in stream order. Without * an end boundary, `numItems` limits emitted values. With `endCursor`, the page * covers the pinned range regardless of its item count, keeping adjacent * reactive pages gap-free as data changes. See `PaginateOptions` for read * limits and the special case of a zero-sized request. * * Bounds are pushed into underlying index queries where possible, but composed * streams may read more documents than they return, including documents read on * earlier pages. Budgets count filtered documents, distinct-group discovery, * repeated seeks, merge prefetch, and both outer and inner query-stream reads * in joins. Arbitrary I/O inside callbacks, such as a separate lookup in * `mapEffect`, is not tracked. * * A budget-limited page returns `SplitRequired` at a safe logical boundary. If * the budget prevents safe progress or an interior split of a pinned page, the * effect fails with `ReadBudgetExceededError` rather than returning a * non-advancing cursor. Increase the budget or reduce the reads needed for * progress. See `PaginationResult` for split and completion handling. * * Malformed or incompatible cursors die with a `ConvexError` whose data is `{ * paginationError: "InvalidCursor" }`; Confect's pagination clients restart * from the first page. Restart pagination whenever filters, ordering, or other * query semantics change, even if an old cursor is still accepted. * * Cursors expose boundary field names and key values, including document IDs. * They are neither opaque nor signed: clients can read or craft them. Do not * publicly paginate over sensitive indexed fields unless pinned with `eq`. * Never construct cursors from documents or decode them into `narrow` bounds. * * Return the whole result to reactive clients. React's * `useStreamPaginatedQuery` handles pinning, splitting, and invalid-cursor * resets; `usePaginatedQuery` does not support stream pages. Foldkit's * `PaginatedQuery` also supports them. * * @experimental */ export declare const paginate: { (options: PaginateOptions): (self: QueryStream) => Effect.Effect, E | ReadBudgetExceededError, R>; (self: QueryStream, options: PaginateOptions): Effect.Effect, E | ReadBudgetExceededError, R>; }; //# sourceMappingURL=QueryStream.d.ts.map