grafast
Version:
Cutting edge GraphQL planning and execution engine
399 lines • 20.2 kB
TypeScript
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