UNPKG

grafast

Version:

Cutting edge GraphQL planning and execution engine

399 lines 20.2 kB
import type { BaseGraphQLArguments, ExecutionDetails, ExecutionDetailsStream, FieldArgs, GrafastResultsList, Maybe, PromiseOrDirect, StepOptimizeOptions, UnbatchedExecutionExtra } from "../interfaces.js"; import { Step, UnbatchedStep } from "../step.js"; import { ConstantStep } from "./constant.js"; /** * 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<TCursorValue = string> { /** * 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<TItem, TNodeStep extends Step = Step<TItem>, TEdgeStep extends EdgeCapableStep<TItem, TNodeStep> = EdgeStep<TItem, TNodeStep>, 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<PaginationParams<TCursorValue>>): void; /** * Clone the plan, ignoring the pagination parameters. * * Useful for implementing things like `totalCount` or aggregates. */ connectionClone?(...args: any[]): ConnectionOptimizedStep<TItem, TNodeStep, TEdgeStep, TCursorValue>; /** * 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<Maybe<string>>): Step<Maybe<TCursorValue>>; /** * If the `$item` represents an edge rather than the node, return the node to * use instead. */ nodeForItem?($item: Step<TItem>): TNodeStep; /** * Turn a value from the list into an item step */ edgeForItem?($item: Step<MaybeIndexed<TItem>>): TEdgeStep; /** * Used as a fallback if nodeForItem isn't implemented. */ listItem?($item: Step<TItem>): TNodeStep; /** * Given the $item, return a step representing the cursor. * * Must be implemented if and only if `paginationSupport.cursor` is `true`. */ cursorForItem?($item: Step<TItem>): Step<string>; } /** The result of a "ConnectionHandlingStep". */ export interface ConnectionHandlingResult<TItem> { hasNextPage: PromiseOrDirect<boolean>; hasPreviousPage: PromiseOrDirect<boolean>; items: ReadonlyArray<TItem> | AsyncIterable<TItem>; } export interface ConnectionHandlingStep<TItem, TNodeStep extends Step = Step<TItem>, TEdgeStep extends EdgeCapableStep<TItem, TNodeStep> = EdgeStep<TItem, TNodeStep>, TCursorValue = string> extends Step<Maybe<ConnectionHandlingResult<TItem>>>, Pick<ConnectionOptimizedStep<TItem, TNodeStep, TEdgeStep, TCursorValue>, "parseCursor" | "nodeForItem" | "edgeForItem" | "listItem">, Required<Pick<ConnectionOptimizedStep<TItem, TNodeStep, TEdgeStep, TCursorValue>, "cursorForItem">> { /** * Clone the plan, ignoring the pagination parameters. * * Useful for implementing things like `totalCount` or aggregates. */ connectionClone?(...args: any[]): ConnectionHandlingStep<TItem, TNodeStep, TEdgeStep, TCursorValue>; paginationSupport: { full: true; }; setFirst($first: Step<Maybe<number>>): void; setLast($last: Step<Maybe<number>>): void; setOffset($offset: Step<Maybe<number>>): void; setBefore($before: Step<Maybe<TCursorValue>>): void; setAfter($after: Step<Maybe<TCursorValue>>): 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<ExecutionDetailsStream | null> | null): void; } interface Indexed<TItem> { index: number; item: TItem; } type MaybeIndexed<TItem> = TItem | Indexed<TItem>; interface ConnectionResult<TItem> { hasNextPage: PromiseOrDirect<boolean>; hasPreviousPage: PromiseOrDirect<boolean>; items: ReadonlyArray<MaybeIndexed<TItem>> | Iterable<MaybeIndexed<TItem>> | AsyncIterable<MaybeIndexed<TItem>>; } interface EdgeCapableStep<TItem, TNodeStep extends Step, TEdgeDataStep extends Step = Step<TItem>> extends Step { node(): TNodeStep; cursor(): Step<string>; data(): TEdgeDataStep; } export type StepRepresentingList<TItem, TNodeStep extends Step = Step<TItem>, TEdgeStep extends EdgeCapableStep<TItem, TNodeStep> = EdgeStep<TItem, TNodeStep>, TCursorValue = string> = ConnectionOptimizedStep<TItem, TNodeStep, TEdgeStep, TCursorValue> | StepWithItems<TItem> | Step<Maybe<readonly TItem[]>>; /** * Handles GraphQL cursor pagination in a standard and consistent way * indepdenent of data source. */ export declare class ConnectionStep<TItem, TNodeStep extends Step = Step<TItem>, TEdgeDataStep extends Step = Step<TItem>, TEdgeStep extends EdgeCapableStep<TItem, TNodeStep, TEdgeDataStep> = EdgeStep<TItem, TNodeStep, TEdgeDataStep>, TCursorValue = string, TCollectionStep extends StepRepresentingList<TItem, TNodeStep, TEdgeStep, TCursorValue> = StepRepresentingList<TItem, TNodeStep, TEdgeStep, TCursorValue>> extends Step<ConnectionResult<TItem> | 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<TItem>) => TEdgeDataStep; constructor(subplan: TCollectionStep, params?: Omit<ConnectionParams<any, TItem, TEdgeDataStep>, "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<number | null | undefined> | null; setFirst(first: Step<number | null | undefined> | number): void; getLast(): Step<number | null | undefined> | null; setLast(last: Step<number | null | undefined> | number): void; getOffset(): Step<number | null | undefined> | null; setOffset(offset: Step<number | null | undefined> | number): void; getBefore(): Step<Maybe<TCursorValue>> | null; setBefore($beforePlan: Step<Maybe<string>>): void; getAfter(): Step<Maybe<TCursorValue>> | null; setAfter($afterPlan: Step<Maybe<string>>): 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<TCollectionStep extends ConnectionOptimizedStep<TItem, TNodeStep, TEdgeStep, TCursorValue> | ConnectionHandlingStep<TItem, TNodeStep, TEdgeStep, TCursorValue> ? Exclude<TCollectionStep["connectionClone"], undefined> : 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<any>; listItem($rawItem: Step<TItem>): TNodeStep; getUnindexedItem($rawItem: Step<MaybeIndexed<TItem>>): Step<TItem>; nodePlan: ($rawItem: Step<MaybeIndexed<TItem>>) => TNodeStep; edgePlan: ($rawItem: Step<MaybeIndexed<TItem>>) => TEdgeStep | EdgeStep<TItem, TNodeStep, TEdgeStep>; private captureStream; edges(): Step; nodes(): import("./listTransform.js").__ListTransformStep<any, any, any, any>; items(): import("./listTransform.js").__ListTransformStep<any, any, any, any>; cursorPlan($rawItem: Step<MaybeIndexed<TItem>>): Step<string> | import("./lambda.js").LambdaStep<readonly [number, number, number], string>; pageInfo(): PageInfoStep; optimize(): this | ConstantStep<ConnectionResult<never>>; deduplicatedWith(replacement: ConnectionStep<any, any, any, any, any>): void; execute({ values, indexMap, }: ExecutionDetails): GrafastResultsList<ConnectionResult<TItem> | null>; } export declare class EdgeStep<TItem, TNodeStep extends Step = Step, TEdgeDataStep extends Step = Step<TItem>> extends UnbatchedStep { static $$export: { moduleName: string; exportName: string; }; isSyncAndSafe: boolean; private connectionRefId; constructor($connection: ConnectionStep<TItem, TNodeStep, any, any, any>, $rawItem: Step<MaybeIndexed<TItem>>); get(fieldName: string): ConstantStep<undefined> | Step<string> | TNodeStep | TEdgeDataStep; private getConnectionStep; private getRawItemStep; private getItemStep; data(): TEdgeDataStep; node(): TNodeStep; private cursorDepId; cursor(): Step<string>; deduplicate(_peers: EdgeStep<any, any>[]): EdgeStep<TItem, TNodeStep>[]; unbatchedExecute(_extra: UnbatchedExecutionExtra, record: any, cursor: any): any; } interface ConnectionParams<TObj extends BaseGraphQLArguments = any, TItem = any, TEdgeDataStep extends Step = Step<TItem>> { fieldArgs?: FieldArgs<TObj>; edgeDataPlan?: ($item: Step<TItem>) => TEdgeDataStep; } /** * Wraps a collection fetch to provide the utilities for working with GraphQL * cursor connections. */ export declare function connection<TItem, TNodeStep extends Step = Step<TItem>, TEdgeDataStep extends Step = Step<TItem>, TEdgeStep extends EdgeCapableStep<TItem, TNodeStep, TEdgeDataStep> = EdgeStep<TItem, TNodeStep, TEdgeDataStep>, TCursorValue = any, TCollectionStep extends StepRepresentingList<TItem, TNodeStep, TEdgeStep, TCursorValue> = StepRepresentingList<TItem, TNodeStep, TEdgeStep, TCursorValue>, TFieldArgs extends BaseGraphQLArguments = any>(step: TCollectionStep, params?: ConnectionParams<TFieldArgs, TItem, TEdgeDataStep>): ConnectionStep<TItem, TNodeStep, TEdgeDataStep, TEdgeStep, TCursorValue, TCollectionStep>; interface StepWithItems<TItem = any> extends Step { items(): Step<Maybe<ReadonlyArray<TItem>>>; } export type ItemsStep<TStep extends StepRepresentingList<any>> = TStep extends StepWithItems ? ReturnType<TStep["items"]> : TStep; export declare function itemsOrStep<T extends Step<Maybe<readonly any[]>> | StepWithItems>($step: T): Step<Maybe<readonly any[]>>; export declare class ConnectionParamsStep<TCursorValue> extends UnbatchedStep<PaginationParams<TCursorValue>> { private paginationSupport; 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; constructor(paginationSupport: PaginationFeatures | null); setFirst($first: Step<Maybe<number>>): void; setLast($last: Step<Maybe<number>>): void; setOffset($offset: Step<Maybe<number>>): void; setBefore($before: Step<Maybe<TCursorValue>>): void; setAfter($after: Step<Maybe<TCursorValue>>): void; setNeedsHasMore(): void; addStreamDetails($details: Step<ExecutionDetailsStream | null> | 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<any>[]): ConnectionParamsStep<TCursorValue>[]; deduplicatedWith(replacement: ConnectionParamsStep<any>): void; unbatchedExecute(extra: UnbatchedExecutionExtra, ...values: any[]): PaginationParams<TCursorValue>; } declare class PageInfoStep extends UnbatchedStep<ConnectionResult<any>> { static $$export: { moduleName: string; exportName: string; }; isSyncAndSafe: boolean; constructor($connection: ConnectionStep<any, any, any, any, any, any>); get(key: string): ConstantStep<undefined> | Step<string> | import("./lambda.js").LambdaStep<readonly [number, number, number], string> | import("./access.js").AccessStep<PromiseOrDirect<boolean> | undefined>; unbatchedExecute(_extra: UnbatchedExecutionExtra, _connection: ConnectionResult<any>): ConnectionResult<any>; } export declare class ConnectionItemsStep extends Step { static $$export: { moduleName: string; exportName: string; }; isSyncAndSafe: boolean; cloneStreams: boolean; constructor($connection: ConnectionStep<any, any, any, any, any, any>); getConnection(): ConnectionStep<any, any, any, any, any, any>; optimize(_options: StepOptimizeOptions): Step; deduplicate(_peers: readonly ConnectionItemsStep[]): readonly ConnectionItemsStep[]; execute(executionDetails: ExecutionDetails): readonly any[]; } export {}; //# sourceMappingURL=connection.d.ts.map