import type { BaseGraphQLArguments, ExecutionDetails, ExecutionDetailsStream, FieldArgs, GrafastResultsList, Maybe, PromiseOrDirect, StepOptimizeOptions, UnbatchedExecutionExtra } from "../interfaces.ts"; import { Step, UnbatchedStep } from "../step.ts"; import { ConstantStep } from "./constant.ts"; /** * Indicates which features are supported for pagination; support for `limit` * is assumed even in the case of an empty object. Features unsupported can be * emulated by the `connection()` step (but this is less efficient than * handling it yourself). */ export interface PaginationFeatures { /** * If you want to support reverse pagination, supporting `reverse` is vital. * Without it, we must fetch the entire collection (no limit, offset, or * cursor) in order to access the last entries. * * If you support `reverse` you MUST also support `cursor`, otherwise a * request such as `last: 3` cannot determine the cursors to use (since we * would need to know the length of the collection), and since cursors must * be the same values whether paginating forwards or backwards. If you cannot * support cursor, then simply do not indicate that you support `reverse` and * `connection()` will take care of pagination for you. * * To support `reverse`, you need to be able to apply the other supported * params from the end of the collection working backwards (`after` becomes * `before`, `offset` skips the nodes at the end instead of the start, * `limit` is applied such that it limits from the end rather than from the * start). One way to implement this is to reverse the ordering, apply the * params as normal, and then reverse the ordering back again. Do **NOT** * return the list in reverse order. * * It is recommended that if you do not support `reverse` that you do not * expose the reverse pagination arguments (`before` and `last`) as part of a * GraphQL schema, since `connection()` will need to fetch your entire * collection to implement pagination. */ reverse?: boolean; /** * If you support cursor pagination you must provide a * `.cursorForItem($item)` method that returns the cursor to use for * the given item, and we will assume yout support (and you must support) the * `after` parameter in `PaginationParams`. * * If this is true but `reverse` is not, we will reject all attempts to do * reverse pagination (because a cursor cannot be applied by us, even if we * were to fetch the full collection). You should therefore strongly consider * implementing `reverse` support, or simply do not allow reverse pagination * through your schema. */ cursor?: boolean; /** * If you support offset pagination, set this to true. If you also set * `cursor` to true, then the offset must apply _from_ the cursor; if have * `cursor` enabled and do not support combining `offset` and `cursor` * together then you should set `offset` to `false` (and raise an issue so we * can add new options for that). */ offset?: boolean; /** * Set this if your collection supports every feature of GraphQL pagination, * and implements the `ConnectionHandlingStep` interface. Instead of caling * `applyPagination` as we do for `ConnectionOptimizedStep`, we will pass * each individual parameter through to your collection step. Your collection * step is also responsible for implementing the * `hasNextPage`/`hasPreviousPage` logic, and must yield from execution an * object conforming to `ConnectionHandlingResult`. */ full?: boolean; } export interface PaginationParams { /** * Fetch only this many rows. If null, apply no limit. */ limit: number | null; /** * Skip this many rows. Always null unless `paginationSupport.offset` is `true`. * * If `after` is set, the offset applies after the `after`. */ offset: number | null; /** * A cursor representing the "exclusive lower bound" for the results - * skip over anything up to and including this cursor. Always `null` unless * `paginationSupport.cursor` is `true`. * * Note: if `reverse` is `true`, this applies in the reverse direction (i.e. * if `after` becomes equivalent to the `before` argument exposed through * GraphQL). */ after: TCursorValue | null; /** * If we're paginating backwards then the collection should apply the other * parameters (`limit`, `offset`, `after`) in reverse (from the end of the * collection). Always `false` unless `paginationSupport.cursor` is `true`. */ reverse: boolean; /** * This will be non-null if it's desirable for the underlying step to stream. * Underlying step should detect whether to stream or not from * `(executionDetails.stream ?? params?.stream)` - works exactly as * `executionDetails.stream`. */ stream: ExecutionDetailsStream | null; } /** * Describes what a plan may implement for ConnectionStep to be able to * utilise it in the most optimal way. * * Implementing this is optional, but: * * - `paginationSupport` should be set (even an empty object) if your data * source supports setting a limit * - `paginationSupport.reverse` should be implemented if you plan to support * reverse pagination (`before`, `last` arguments in GraphQL pagination) * - either `paginationSupport.offset` or `paginationSupport.cursor` are * highly recommended for efficiency * * Be sure to read the documentation of the features you indicate support for! * * @param TItem - The data represented by an individual list item * @param TNodeStep - A derivative of TEdgeStep that represents the _node_ itself. Defaults to TEdgeStep. * @param TEdgeStep - Represents an item in the collection, typically equivalent to an _edge_. * @param TCursorValue - If your step wants us to parse the cursor for you, the result of the parse. Useful for throwing an error before the fetch if the cursor is invalid. */ export interface ConnectionOptimizedStep, TEdgeStep extends EdgeCapableStep = EdgeStep, TCursorValue = string> extends Step { /** * If set, we assume that you support at least `limit` pagination, even on an * empty object. * * Must not be set without also implementing `applyPagination` and `connectionClone`. */ paginationSupport: PaginationFeatures; /** * Receives the pagination parameters that were declared to be supported by * `paginationSupport`. * * Must not be implemented without also adding `paginationSupport` and `connectionClone`. */ applyPagination($params: Step>): void; /** * Clone the plan, ignoring the pagination parameters. * * Useful for implementing things like `totalCount` or aggregates. */ connectionClone?(...args: any[]): ConnectionOptimizedStep; /** * Optionally implement this and we will parse the cursor for you before * handing it to `applyPagination`. * * Must not be added unless `paginationSupport.cursor` is `true`. */ parseCursor?($cursor: Step>): Step>; /** * If the `$item` represents an edge rather than the node, return the node to * use instead. */ nodeForItem?($item: Step): TNodeStep; /** * Turn a value from the list into an item step */ edgeForItem?($item: Step>): TEdgeStep; /** * Used as a fallback if nodeForItem isn't implemented. */ listItem?($item: Step): TNodeStep; /** * Given the $item, return a step representing the cursor. * * Must be implemented if and only if `paginationSupport.cursor` is `true`. */ cursorForItem?($item: Step): Step; } /** The result of a "ConnectionHandlingStep". */ export interface ConnectionHandlingResult { hasNextPage: PromiseOrDirect; hasPreviousPage: PromiseOrDirect; items: ReadonlyArray | AsyncIterable; } export interface ConnectionHandlingStep, TEdgeStep extends EdgeCapableStep = EdgeStep, TCursorValue = string> extends Step>>, Pick, "parseCursor" | "nodeForItem" | "edgeForItem" | "listItem">, Required, "cursorForItem">> { /** * Clone the plan, ignoring the pagination parameters. * * Useful for implementing things like `totalCount` or aggregates. */ connectionClone?(...args: any[]): ConnectionHandlingStep; paginationSupport: { full: true; }; setFirst($first: Step>): void; setLast($last: Step>): void; setOffset($offset: Step>): void; setBefore($before: Step>): void; setAfter($after: Step>): void; /** * Called when `hasNextPage`/`hasPreviousPage` are requested. */ setNeedsHasMore(): void; /** * Passes the stream options that this collection is accessed with. This may * happen more than once (e.g. if you have * `nodes @stream(...) {...}, edges @stream(...) {...}`). If so, `null` wins, * and failing that, the largest `initialCount` wins. * * WARNING: This may be called more than once! */ addStreamDetails?($streamDetails: Step | null): void; } interface Indexed { index: number; item: TItem; } type MaybeIndexed = TItem | Indexed; interface ConnectionResult { hasNextPage: PromiseOrDirect; hasPreviousPage: PromiseOrDirect; items: ReadonlyArray> | Iterable> | AsyncIterable>; } interface EdgeCapableStep> extends Step { node(): TNodeStep; cursor(): Step; data(): TEdgeDataStep; } export type StepRepresentingList, TEdgeStep extends EdgeCapableStep = EdgeStep, TCursorValue = string> = ConnectionOptimizedStep | StepWithItems | Step>; /** * Handles GraphQL cursor pagination in a standard and consistent way * indepdenent of data source. */ export declare class ConnectionStep, TEdgeDataStep extends Step = Step, TEdgeStep extends EdgeCapableStep = EdgeStep, TCursorValue = string, TCollectionStep extends StepRepresentingList = StepRepresentingList> extends Step | null> { static $$export: { moduleName: string; exportName: string; }; isSyncAndSafe: boolean; private neededCollection; private collectionDepId; /** * null = unknown */ private _mightStream; private _firstDepId; private _lastDepId; private _offsetDepId; private _beforeDepId; private _afterDepId; /** If `null` we **must not** mess with this.getSubplan() */ private collectionPaginationSupport; /** If the user asks for details of `hasNextPage`/`hasPreviousPage`, then fetch one extra */ private needsHasMore; private needsCursor; private paramsDepId; edgeDataPlan: ($rawItem: Step) => TEdgeDataStep; constructor(subplan: TCollectionStep, params?: Omit, "fieldArgs">); mightStream(): boolean; getSubplan(): TCollectionStep; private _getSubplan; /** * This represents a single page from the collection - not only have * conditions and ordering been applied but we've also applied the pagination * constraints (before, after, first, last, offset). It's useful for * returning the actual edges and nodes of the connection. * * This cannot be called before the arguments have been finalized. */ private setupSubplanWithPagination; private getHandler; toStringMeta(): string; setNeedsHasMore(): void; getFirst(): Step | null; setFirst(first: Step | number): void; getLast(): Step | null; setLast(last: Step | number): void; getOffset(): Step | null; setOffset(offset: Step | number): void; getBefore(): Step> | null; setBefore($beforePlan: Step>): void; getAfter(): Step> | null; setAfter($afterPlan: Step>): void; /** * This represents the entire collection with conditions and ordering * applied, but without any pagination constraints (before, after, first, * last, offset) applied. It's useful for the following: * * - performing aggregates e.g. totalCount across the entire collection * - determining fields for pageInfo, e.g. is there a next/previous page * * This cannot be called before the arguments have been finalized. */ cloneSubplanWithoutPagination(...args: Parameters | ConnectionHandlingStep ? Exclude : never>): TCollectionStep; private paginationParams; /** * Subplans may call this from their `setBefore`/`setAfter`/etc plans in order * to add a dependency to us, which is typically useful for adding validation * functions so that they are thrown "earlier", avoiding error bubbling. */ addValidation(callback: () => Step): void; get(fieldName: string): Step; listItem($rawItem: Step): TNodeStep; getUnindexedItem($rawItem: Step>): Step; nodePlan: ($rawItem: Step>) => TNodeStep; edgePlan: ($rawItem: Step>) => TEdgeStep | EdgeStep; private captureStream; edges(): Step; nodes(): import("./listTransform.ts").__ListTransformStep; items(): import("./listTransform.ts").__ListTransformStep; cursorPlan($rawItem: Step>): Step | import("./lambda.ts").LambdaStep; pageInfo(): PageInfoStep; optimize(): this | ConstantStep>; deduplicatedWith(replacement: ConnectionStep): void; execute({ values, indexMap, }: ExecutionDetails): GrafastResultsList | null>; } export declare class EdgeStep> extends UnbatchedStep { static $$export: { moduleName: string; exportName: string; }; isSyncAndSafe: boolean; private connectionRefId; constructor($connection: ConnectionStep, $rawItem: Step>); get(fieldName: string): Step | ConstantStep | TNodeStep | TEdgeDataStep; private getConnectionStep; private getRawItemStep; private getItemStep; data(): TEdgeDataStep; node(): TNodeStep; private cursorDepId; cursor(): Step; deduplicate(_peers: EdgeStep[]): EdgeStep[]; unbatchedExecute(_extra: UnbatchedExecutionExtra, record: any, cursor: any): any; } interface ConnectionParams> { fieldArgs?: FieldArgs; edgeDataPlan?: ($item: Step) => TEdgeDataStep; } /** * Wraps a collection fetch to provide the utilities for working with GraphQL * cursor connections. */ export declare function connection, TEdgeDataStep extends Step = Step, TEdgeStep extends EdgeCapableStep = EdgeStep, TCursorValue = any, TCollectionStep extends StepRepresentingList = StepRepresentingList, TFieldArgs extends BaseGraphQLArguments = any>(step: TCollectionStep, params?: ConnectionParams): ConnectionStep; interface StepWithItems extends Step { items(): Step>>; } export type ItemsStep> = TStep extends StepWithItems ? ReturnType : TStep; export declare function itemsOrStep> | StepWithItems>($step: T): Step>; export declare class ConnectionParamsStep extends UnbatchedStep> { static $$export: { moduleName: string; exportName: string; }; /** sync and safe because it's unary; an error thrown for one is thrown for all */ isSyncAndSafe: boolean; private needsHasMore; private firstDepId; private lastDepId; private offsetDepId; private beforeDepId; private afterDepId; private streamDetailsDepIds; private paginationSupport; constructor(paginationSupport: PaginationFeatures | null); setFirst($first: Step>): void; setLast($last: Step>): void; setOffset($offset: Step>): void; setBefore($before: Step>): void; setAfter($after: Step>): void; setNeedsHasMore(): void; addStreamDetails($details: Step | null): void; /** * True if it's possible this'll stream, false if we've not * been told anything about streaming. */ mightStream(): boolean; deduplicate(peers: ConnectionParamsStep[]): ConnectionParamsStep[]; deduplicatedWith(replacement: ConnectionParamsStep): void; unbatchedExecute(extra: UnbatchedExecutionExtra, ...values: any[]): PaginationParams; } declare class PageInfoStep extends UnbatchedStep> { static $$export: { moduleName: string; exportName: string; }; isSyncAndSafe: boolean; constructor($connection: ConnectionStep); get(key: string): Step | ConstantStep | import("./lambda.ts").LambdaStep | import("./access.ts").AccessStep | undefined>; unbatchedExecute(_extra: UnbatchedExecutionExtra, _connection: ConnectionResult): ConnectionResult; } export declare class ConnectionItemsStep extends Step { static $$export: { moduleName: string; exportName: string; }; isSyncAndSafe: boolean; cloneStreams: boolean; constructor($connection: ConnectionStep); getConnection(): ConnectionStep; optimize(_options: StepOptimizeOptions): Step; deduplicate(_peers: readonly ConnectionItemsStep[]): readonly ConnectionItemsStep[]; execute(executionDetails: ExecutionDetails): readonly any[]; } export {}; //# sourceMappingURL=connection.d.ts.map