UNPKG

grafast

Version:

Cutting edge GraphQL planning and execution engine

297 lines 14.4 kB
import { $$subroutine } from "./constants.js"; import type { LayerPlan, LayerPlanReasonSubroutine } from "./engine/LayerPlan.js"; import type { OperationPlan } from "./engine/OperationPlan.js"; import type { AddDependencyOptions, AddUnaryDependencyOptions, DependencyOptions, ExecutionDetails, ExecutionEntryFlags, ExecutionResults, GrafastResultsList, JSONValue, Maybe, PromiseOrDirect, StepOptimizeOptions, UnbatchedExecutionExtra } from "./interfaces.js"; import type { __FlagStep, __ItemStep } from "./steps/index.js"; declare function reallyAssertFinalized(plan: Step): void; export declare const assertFinalized: typeof reallyAssertFinalized; /** * Executable plans are the plans associated with leaves on the GraphQL tree, * they must be able to execute to return values. */ export declare class Step<TData = any> { readonly operationPlan: OperationPlan; [$$subroutine]: LayerPlan<LayerPlanReasonSubroutine> | null; isArgumentsFinalized: boolean; isFinalized: boolean; debug: boolean; static $$export: any; /** * Setting this true is a performance optimisation, but it comes with strong * rules; we do not test you comply with these rules (as that would undo the * performance gains) but should you break them the behaviour is undefined * (and, basically, the schema may no longer be GraphQL compliant). * * Do not set this true unless the following hold: * * - The `execute` method must be a regular (not async) function * - The `execute` method must NEVER return a promise * - The values within the list returned from `execute` must NEVER include * promises or FlaggedValue objects * - The result of calling `execute` should not differ after a * `step.hasSideEffects` has executed (i.e. it should be pure, only * dependent on its deps and use no external state) * * It's acceptable for the `execute` method to throw if it needs to. * * This optimisation applies to the majority of the built in plans and allows * the engine to execute without needing to resolve any promises which saves * precious event-loop ticks. */ isSyncAndSafe: boolean; /** * (default = ALL_FLAGS & ~FLAG_NULL) */ protected readonly defaultForbiddenFlags: ExecutionEntryFlags; get dependencyCount(): number; /** * Unique identifier for this step within the plan. */ readonly id: number; /** /** * True when `optimize` has been called at least once. */ isOptimized: boolean; /** * Set this true if your plan's optimize method can be called a second time; * note that in this situation it's likely that your dependencies will not be * what you expect them to be (e.g. a PgSelectSingleStep might become an * AccessStep). */ allowMultipleOptimizations: boolean; /** * Set the metaKey so `execute` will be passed a meta object to use. * Depending on what you set it to, you can share execution meta between * multiple steps of the same class (or even a family of step classes). * * A sensible value for it is `this.metaKey = this.id;`. */ metaKey: number | string | symbol | undefined; /** * Like `metaKey` but for the optimize phase */ optimizeMetaKey: number | string | symbol | undefined; /** * If the peerKey of two steps do not match, then they are definitely not * peers. Use this to reduce the load on deduplicate by more quickly * eradicating definitely-not-peers. * * Note: we may well change this to be a function in future, so it's advised * that you don't use this unless you're working inside the graphile/crystal * core codebase. * * @experimental */ peerKey: string | null; /** * Set this true for plans that implement mutations; this will prevent them * from being tree-shaken. */ hasSideEffects: boolean; /** * Set this to `true` if this step might return an iterable or async iterable * that cannot be consumed more than once. Grafast will wrap such values in a * "distributor" to allow multiple downstream steps to independently[^1] * consume clones of the stream. Grafast will not wrap arrays in this way as * doing so is unnecessary. * * [^1]: To avoid excessive memory consumption, if a clone gets * `distributorTargetBufferSize` items further ahead than another clone, it * will be temporarily paused (for up to `distributorPauseDuration` * milliseconds) to give the slowest clone a chance to catch up. * * WARNING: Cloning an async iterable only clones its iterable behavior; * other methods and properties are not preserved. For example, if you return * a `Map` with `cloneStreams: true`, downstream consumers will not have * access to `.get(key)`, `.size`, or similar methods/properties. * * WARNING: This transform always produces _async_ iterables, even if the * original was synchronous. This enables pausing of fast consumers whilst * slower consumers catch up, minimizing memory pressure. */ cloneStreams: boolean; constructor(); /** * Generally you should only use this once the dependencies of a step are * established, if you use it beforehand and it returns `true` then adding a * non-unary dependency later will result in an error. */ getAndFreezeIsUnary(): boolean; protected withMyLayerPlan<T>(callback: () => T): T; /** @experimental */ withLayerPlan<T>(callback: () => T): T; protected getStep(id: number): Step; protected getDepOptions<TStep extends Step = Step>(depId: number): DependencyOptions<TStep>; protected _getDepOptions<TStep extends Step = Step>(depId: number): DependencyOptions<TStep>; protected getDep<TStep extends Step = Step>(_depId: number): TStep | __FlagStep<TStep>; protected getDep<TStep extends Step = Step>(_depId: number, throwOnFlagged: true): TStep; protected maybeGetDep<TStep extends Step = Step>(depId: number | null | undefined): TStep | __FlagStep<TStep> | null; protected maybeGetDep<TStep extends Step = Step>(depId: number | null | undefined, throwOnFlagged: true): TStep | null; protected getDepOrConstant<TData = any>(_depId: number | null, _fallback: TData): Step<TData>; /** * Like getDep, except it skips over __ItemStep and similar steps to get to * where the parent really is. * * @experimental */ protected getDepDeep(depId: number): Step; /** * Cache a generated step by a given identifier (cacheKey) such that we don't * need to regenerate it on future calls, significantly reducing the load on * deduplication later. * * @experimental */ protected cacheStep<T extends Step>(actionKey: string, cacheKey: symbol | string | number, cb: () => T): T; toString(): string; /** * This metadata will be merged into toString when referencing this plan. */ toStringMeta(): string | null; planJSONExtra(): Record<string, JSONValue | undefined> | undefined; /** * **IF IN DOUBT, USE `.addDependency()` INSTEAD! * * This **DANGEROUS** method allows you to create a reference to another * step. A reference is like a dependency except it has no runtime impact - * the data is not passed into execute. In general you should only add * references to steps that you directly or indirectly depend on (e.g. an * ancestor step) so that you may communicate with said step during * `optimize` (for example). Sometimes it's acceptable to reference steps * that you don't transitively depend on; in those cases you're permitted to * pass an `allowIndirectReason` to explain to yourself and others why you * are breaking these rules. * * @experimental */ protected addRef(rawStep: Step, allowIndirectReason?: string | null): number | null; /** * Allows you to dereference a reference made via `addRef`. Will resolve to * whatever that step is now (or null if not found). Note that referenced * referenced steps may change to a new step instance due to lifecycle * methods (e.g. deduplicate) or even to an entirely separate class * altogether (e.g. due to optimize). References are not guaranteed to be * honoured. * * @experimental */ protected getRef(refIdx: number | null): Step | null; protected canAddDependency(step: Step): boolean; protected _addDependency(options: AddDependencyOptions): number; protected addDependency(stepOrOptions: Step | AddDependencyOptions): number; protected addDataDependency(stepOrOptions: Step | AddDependencyOptions): number; protected addStrongDependency(stepOrOptions: Step | AddDependencyOptions): number; /** * Adds "unary" dependencies; in `execute({count, values})` you'll receive a * `values[index]` (where `index` is the return value of this function) with * `isBatch = false` so you can use the `values[index].value` property * directly. */ protected addUnaryDependency(stepOrOptions: Step | AddUnaryDependencyOptions): number; /** * Given a list of "peer" steps, return a list of these `peers` that are * equivalent to this step. * * NOTE: equivalence goes both ways: `a.deduplicate([b]).includes(b)` if and * only if `b.deduplicate([a]).includes(a)`. * * If you need to transform the peer to be equivalent you should do so via * the `deduplicatedWith` callback later. */ deduplicate?(_peers: readonly Step[]): readonly Step[]; /** * If this plan is replaced via deduplication, this method gives it a chance * to hand over its responsibilities to its replacement. */ deduplicatedWith?(replacement: Step): void; /** * Our chance to optimise the plan (which could go as far as to inline the * plan into the parent plan). */ optimize?(_options: StepOptimizeOptions): Step; finalize(): void; /** * This function will be called with 'execution details', an object containing: * * - `count`: the number of entries in the batch that's being executed * - `values`: a tuple representing the runtime values of the steps * dependencies; each value in the tuple is an object, either a batch object * containing a list of size `count` containing the values, or a unary * object containing the single value common to all entries. * - `indexMap`: helper function to map over each index from `0` to `count-1`, * returning the resulting array. * - `indexForEach`: as `indexMap`, but without the array result. * - `meta`: [experimental] * * `execute` must return a list with `count` entries, where each value in the * list relates to the result of executing this plan for the corresponding * entry in each of the entries in the `values` tuple. * * IMPORTANT: it is up to the execute function to cache/memoize results as * appropriate for performance, this can be done via the `meta` object. * * The `meta` object is an empty object stored to `grafastContext.metaByPlan` * that can be used to store anything this plan needs. We recommend that you * add attributes to meta for each purpose (e.g. use `meta.cache` for * memoizing results) so that you can expand your usage of meta in future. */ execute(details: ExecutionDetails): ExecutionResults<TData>; destroy(): void; toRecord?(): Step; toSpecifier?(): Step; toTypename?(): Step<string>; } export declare abstract class UnbatchedStep<TData = any> extends Step<TData> { static $$export: { moduleName: string; exportName: string; }; finalize(): void; execute({ indexMap, values, extra, }: ExecutionDetails): GrafastResultsList<TData>; abstract unbatchedExecute(extra: UnbatchedExecutionExtra, ...tuple: any[]): PromiseOrDirect<TData>; } export declare function isStep<TData = any>(step: unknown): step is Step<TData>; export declare function assertStep<TData>(step: unknown): asserts step is Step<TData>; export declare function isUnbatchedStep<TData = any>(step: unknown): step is UnbatchedStep<TData>; export type ObjectLikeStep<TData extends { [key: string]: Step; } = { [key: string]: Step; }> = Step<{ [key in keyof TData]: TData[key] extends Step<infer U> ? U : never; }> & { get<TKey extends keyof TData>(key: TKey): Step<TData[TKey]>; }; export declare function isObjectLikeStep<TData extends { [key: string]: Step; } = { [key: string]: Step; }>(plan: Step): plan is ObjectLikeStep<TData>; export type ListLikeStep<TData extends [...Step[]] = [...Step[]]> = Step<{ [key in keyof TData]: TData[key] extends Step<infer U> ? U : never; }> & { at<TKey extends keyof TData>(key: TKey): Step<TData[TKey]>; }; export declare function isListLikeStep<TData extends [...Step[]] = [...Step[]]>(plan: Step): plan is ListLikeStep<TData>; export interface ListCapableStep<TOutputData, TItemStep extends Step<TOutputData> = Step<TOutputData>, TInputData = any> extends Step<Maybe<ReadonlyArray<TInputData>>> { listItem(itemPlan: __ItemStep<TInputData>): TItemStep; } export declare function isListCapableStep<TData, TItemStep extends Step<TData>>(plan: Step<Maybe<ReadonlyArray<TData>>>): plan is ListCapableStep<TData, TItemStep>; export declare function assertListCapableStep<TData, TItemStep extends Step<TData>>(plan: Step<Maybe<ReadonlyArray<TData>>>, pathDescription: string): asserts plan is ListCapableStep<TData, TItemStep>; export declare function stepHasToSpecifier<TStep extends Step>($step: TStep): $step is TStep & { toSpecifier(): Step; }; export declare function stepHasToRecord<TStep extends Step>($step: TStep): $step is TStep & { toRecord(): Step; }; export { /** @deprecated Use ExecutableStep instead */ Step as ExecutableStep, /** @deprecated Use UnbatchedStep instead */ UnbatchedStep as UnbatchedExecutableStep, }; /** @deprecated Use isStep instead */ export declare const isExecutableStep: typeof isStep; /** @deprecated Use isStep instead */ export declare function assertExecutableStep<TData>(step: unknown): asserts step is Step<TData>; //# sourceMappingURL=step.d.ts.map