@graphql-mesh/fusion-runtime
Version:
Runtime for GraphQL Mesh Fusion Supergraph
351 lines (344 loc) • 19.1 kB
text/typescript
import * as _graphql_tools_utils from '@graphql-tools/utils';
import { ExecutionRequest, Executor, TypeSource, IResolvers, MaybePromise as MaybePromise$1, Maybe, MaybeAsyncIterable, ExecutionResult as ExecutionResult$1 } from '@graphql-tools/utils';
import { Logger } from '@graphql-hive/logger';
import { TransportContext, TransportEntry, Transport } from '@graphql-mesh/transport-common';
export { TransportEntry, TransportGetSubgraphExecutor, TransportGetSubgraphExecutorOptions } from '@graphql-mesh/transport-common';
import { DelegationPlanBuilder, Subschema, MergedTypeResolver, SubschemaConfig } from '@graphql-tools/delegate';
import { GraphQLSchema, DocumentNode, ExecutionResult, GraphQLError, FragmentDefinitionNode, SelectionNode, SelectionSetNode } from 'graphql';
import { GraphQLResolveInfo, GraphQLOutputType } from 'graphql/type';
import { OnDelegateHook } from '@graphql-mesh/types';
import { DisposableSymbols } from '@whatwg-node/disposablestack';
import { MaybePromise } from '@whatwg-node/promise-helpers';
import { getBatchingExecutor } from '@graphql-tools/batch-execute';
type TransportEntryAdditions = {
[subgraph: '*' | string]: Partial<TransportEntry>;
};
declare function ensureSchema(source: GraphQLSchema | DocumentNode | string): GraphQLSchema;
/**
* Configure the batch delegation options for all merged types in all subschemas.
*/
interface BatchDelegateOptions {
/**
* Limits the number of items that get requested when performing batch delegation.
* @default Infinity
*/
maxBatchSize?: number;
}
type UnifiedGraphHandler = (opts: UnifiedGraphHandlerOpts) => MaybePromise<UnifiedGraphHandlerResult>;
interface UnifiedGraphHandlerOpts {
unifiedGraph: GraphQLSchema;
getUnifiedGraphSDL(): string;
additionalTypeDefs?: TypeSource;
additionalResolvers?: IResolvers<unknown, any> | IResolvers<unknown, any>[];
onSubgraphExecute: ReturnType<typeof getOnSubgraphExecute>;
handleProgressiveOverride?(label: string, context: any): boolean;
onDelegationPlanHooks?: OnDelegationPlanHook<any>[];
onDelegationStageExecuteHooks?: OnDelegationStageExecuteHook<any>[];
onDelegateHooks?: OnDelegateHook<unknown>[];
/**
* Configure the batch delegation options for all merged types in all subschemas.
*/
batchDelegateOptions?: BatchDelegateOptions;
log?: Logger;
}
interface UnifiedGraphHandlerResult {
unifiedGraph: GraphQLSchema;
executor?: Executor;
getSubgraphSchema(subgraphName: string): GraphQLSchema;
inContextSDK?: any;
overrideLabels?: Iterable<string>;
}
interface UnifiedGraphManagerOptions<TContext> {
getUnifiedGraph(ctx: TransportContext | undefined): MaybePromise<GraphQLSchema | string | DocumentNode>;
handleUnifiedGraph?: UnifiedGraphHandler;
transports?: Transports;
transportEntryAdditions?: TransportEntryAdditions;
/** Schema polling interval in milliseconds. */
pollingInterval?: number;
additionalTypeDefs?: TypeSource;
additionalResolvers?: IResolvers<unknown, TContext> | IResolvers<unknown, TContext>[];
transportContext?: TransportContext;
onSubgraphExecuteHooks?: OnSubgraphExecuteHook<TContext>[];
onDelegateHooks?: OnDelegateHook<unknown>[];
onDelegationPlanHooks?: OnDelegationPlanHook<TContext>[];
onDelegationStageExecuteHooks?: OnDelegationStageExecuteHook<TContext>[];
/**
* Whether to batch the subgraph executions.
* @default true
*/
batch?: boolean;
/**
* Configure the batch delegation options for all merged types in all subschemas.
*/
batchDelegateOptions?: BatchDelegateOptions;
instrumentation?: () => Instrumentation | undefined;
onUnifiedGraphChange?(newUnifiedGraph: GraphQLSchema): void;
handleProgressiveOverride?(label: string, context: any): MaybePromise<boolean>;
/**
* When greater than 0, enables "generation overlap" on schema reload: a
* superseded generation (executor + transports) is kept alive so in-flight
* single-result operations can finish, instead of being disposed (and
* aborted) immediately. This is the maximum time, in milliseconds, a
* superseded generation is kept alive before it is force-disposed.
*
* Defaults to disposing immediately (previous behavior).
*/
schemaReloadDrainTimeout?: number;
/**
* Maximum number of schema generations kept alive simultaneously (current +
* draining) when {@link schemaReloadDrainTimeout} is enabled. When exceeded,
* the oldest draining generation is force-disposed.
*
* @default 10
*/
maxConcurrentSchemaGenerations?: number;
}
type Instrumentation = {
/**
* Wrap each subgraph execution request. This can happen multiple time for the same graphql operation.
*/
subgraphExecute?: (payload: {
executionRequest: ExecutionRequest;
subgraphName: string;
}, wrapped: () => MaybePromise<void>) => MaybePromise<void>;
/**
* Wrap each supergraph schema loading.
*
* Note: this span is only available when an Async compatible context manager is available
*/
schema?: (payload: null, wrapped: () => MaybePromise<void>) => MaybePromise<void>;
};
/**
* An operation's hold on the schema generation it was admitted under, returned
* by {@link UnifiedGraphManager.retainGenerationFor}. The operation must
* execute through {@link executor} so that it runs on the same generation it
* pins, and call {@link release} exactly when it fully completes.
*/
interface SchemaGenerationLease {
/**
* The pinned generation's executor, when its unified graph handler produced
* one (e.g. the router runtime). Undefined for the default handler, which
* executes through the schema's own resolvers.
*/
executor: Executor | undefined;
/** Release the pin. Idempotent. */
release(): void;
}
declare class UnifiedGraphManager<TContext> implements AsyncDisposable {
private opts;
private batch;
private handleUnifiedGraph;
private unifiedGraph?;
private lastLoadedUnifiedGraph?;
private onSubgraphExecuteHooks;
private onDelegationPlanHooks;
private onDelegationStageExecuteHooks;
private inContextSDK;
private initialUnifiedGraph$?;
private polling$?;
private _transportEntryMap?;
private lastLoadTime?;
private instrumentation;
private overrideLabelsByContext;
private overrideLabels;
/** The generation currently serving new requests. */
private currentGeneration?;
/** Superseded generations being kept alive until their in-flight work drains. */
private drainingGenerations;
/** Maps each built unified schema to its generation, so an operation can pin
* the generation it executes against for its whole lifetime. */
private generationBySchema;
/** Latch so the "schema not tracked → graceful reload has no effect" warning
* is emitted at most once instead of per request. */
private warnedUntrackedSchema;
constructor(opts: UnifiedGraphManagerOptions<TContext>);
private ensureUnifiedGraph;
private disposeReason;
private handleLoadedUnifiedGraph;
/**
* Pin the generation serving `schema` for the lifetime of one operation, so it
* is not disposed while the operation is still running across all of its
* subgraph hops. Returns a lease exposing the pinned generation's executor —
* the operation must execute through it (not through the current generation's,
* which may already be a newer one) — and an idempotent release callback.
* Returns undefined if `schema` is not a known live generation; the operation
* then proceeds unpinned on the current generation, and disposal falls back to
* the drain timeout.
*
* Long-lived streaming operations (subscriptions) are intentionally NOT pinned
* by the caller — rather than overlapping indefinitely, they end (and the
* client reconnects) when their superseded generation is disposed: right away
* when it is idle, otherwise once it finishes draining.
*/
retainGenerationFor(schema: GraphQLSchema): SchemaGenerationLease | undefined;
private releaseGeneration;
/**
* Retire a generation that has just been superseded by a reload. With graceful
* reload disabled it is disposed immediately (aborting in-flight operations,
* the previous behavior). Otherwise it is kept alive so in-flight operations
* can finish, bounded by a hard-stop timer and a cap on live generations.
*/
private retirePreviousGeneration;
/**
* Force-dispose the oldest draining generations until the number of live
* generations (the current one plus draining ones) is within the cap.
*/
private enforceGenerationCap;
private forceDisposeGeneration;
private disposeGeneration;
private getAndSetUnifiedGraph;
getUnifiedGraph(): MaybePromise<GraphQLSchema>;
getExecutor(): MaybePromise<Executor | undefined>;
getContext<T extends {} = {}>(base?: T): MaybePromise<T>;
getTransportEntryMap(): MaybePromise<Record<string, TransportEntry<Record<string, any>>>>;
invalidateUnifiedGraph(): MaybePromise<GraphQLSchema>;
[DisposableSymbols.asyncDispose](): Promise<void>;
}
type Transports = {
[key: string]: MaybePromise$1<Transport | {
default: Transport;
}>;
} | ((kind: string) => MaybePromise$1<Transport | {
default: Transport;
}>);
/**
* This function creates a executor factory that uses the transport packages,
* and wraps them with the hooks
*/
declare function getOnSubgraphExecute({ onSubgraphExecuteHooks, transportContext, transportEntryMap, getSubgraphSchema, transportExecutorStack, transports, getDisposeReason, batch, instrumentation, }: {
onSubgraphExecuteHooks: OnSubgraphExecuteHook[];
transports?: Transports;
transportContext?: TransportContext;
transportEntryMap: Record<string, TransportEntry>;
getSubgraphSchema(subgraphName: string): GraphQLSchema;
transportExecutorStack: AsyncDisposableStack;
getDisposeReason?: () => GraphQLError | undefined;
batch?: boolean;
instrumentation: () => Instrumentation | undefined;
}): (subgraphName: string, executionRequest: ExecutionRequest) => MaybePromise$1<_graphql_tools_utils.MaybeAsyncIterable<_graphql_tools_utils.ExecutionResult<any, any>>>;
interface WrapExecuteWithHooksOptions {
executor: Executor;
onSubgraphExecuteHooks: OnSubgraphExecuteHook[];
subgraphName: string;
transportEntryMap?: Record<string, TransportEntry>;
getSubgraphSchema: (subgraphName: string) => GraphQLSchema;
transportContext?: TransportContext;
instrumentation: () => Instrumentation | undefined;
}
declare module 'graphql' {
interface GraphQLResolveInfo {
executionRequest?: ExecutionRequest;
}
}
/**
* This function wraps the executor created by the transport package
* with `onSubgraphExecuteHooks` to hook into the execution phase of subgraphs
*/
declare function wrapExecutorWithHooks({ executor: baseExecutor, onSubgraphExecuteHooks, subgraphName, transportEntryMap, getSubgraphSchema, transportContext, instrumentation, }: WrapExecuteWithHooksOptions): Executor;
interface UnifiedGraphPlugin<TContext> {
onSubgraphExecute?: OnSubgraphExecuteHook<TContext>;
onDelegationPlan?: OnDelegationPlanHook<TContext>;
onDelegationStageExecute?: OnDelegationStageExecuteHook<TContext>;
}
type OnSubgraphExecuteHook<TContext = any> = (payload: OnSubgraphExecutePayload<TContext>) => MaybePromise$1<Maybe<OnSubgraphExecuteDoneHook | void>>;
interface OnSubgraphExecutePayload<TContext> {
subgraph: GraphQLSchema;
subgraphName: string;
transportEntry?: TransportEntry;
executionRequest: ExecutionRequest<any, TContext>;
setExecutionRequest(executionRequest: ExecutionRequest): void;
executor: Executor;
setExecutor(executor: Executor): void;
log: Logger;
}
interface OnSubgraphExecuteDonePayload {
result: AsyncIterable<ExecutionResult> | ExecutionResult;
setResult(result: AsyncIterable<ExecutionResult> | ExecutionResult): void;
}
type OnSubgraphExecuteDoneHook = (payload: OnSubgraphExecuteDonePayload) => MaybePromise$1<Maybe<OnSubgraphExecuteDoneResult | void>>;
type OnSubgraphExecuteDoneResultOnNext = (payload: OnSubgraphExecuteDoneOnNextPayload) => MaybePromise$1<void>;
interface OnSubgraphExecuteDoneOnNextPayload {
result: ExecutionResult;
setResult(result: ExecutionResult): void;
}
type OnSubgraphExecuteDoneResultOnEnd = () => MaybePromise$1<void>;
type OnSubgraphExecuteDoneResult = {
onNext?: OnSubgraphExecuteDoneResultOnNext;
onEnd?: OnSubgraphExecuteDoneResultOnEnd;
};
type OnDelegationPlanHook<TContext> = (payload: OnDelegationPlanHookPayload<TContext>) => Maybe<OnDelegationPlanDoneHook | void>;
interface OnDelegationPlanHookPayload<TContext> {
supergraph: GraphQLSchema;
subgraph: string;
sourceSubschema: Subschema<any, any, any, TContext>;
typeName: string;
variables: Record<string, any>;
fragments: Record<string, FragmentDefinitionNode>;
fieldNodes: SelectionNode[];
context: TContext;
log: Logger;
info?: GraphQLResolveInfo;
delegationPlanBuilder: DelegationPlanBuilder;
setDelegationPlanBuilder(delegationPlanBuilder: DelegationPlanBuilder): void;
}
type OnDelegationPlanDoneHook = (payload: OnDelegationPlanDonePayload) => Maybe<void>;
interface OnDelegationPlanDonePayload {
delegationPlan: ReturnType<DelegationPlanBuilder>;
setDelegationPlan: (delegationPlan: ReturnType<DelegationPlanBuilder>) => void;
}
type OnDelegationStageExecuteHook<TContext> = (payload: OnDelegationStageExecutePayload<TContext>) => Maybe<OnDelegationStageExecuteDoneHook>;
interface OnDelegationStageExecutePayload<TContext> {
object: any;
context: TContext;
info: GraphQLResolveInfo;
subgraph: string;
subschema: Subschema<any, any, any, TContext>;
selectionSet: SelectionSetNode;
key?: any;
type: GraphQLOutputType;
resolver: MergedTypeResolver<TContext>;
setResolver: (resolver: MergedTypeResolver<TContext>) => void;
typeName: string;
log: Logger;
}
type OnDelegationStageExecuteDoneHook = (payload: OnDelegationStageExecuteDonePayload) => void;
interface OnDelegationStageExecuteDonePayload {
result: any;
setResult: (result: any) => void;
}
declare function compareSchemas(a: DocumentNode | string | GraphQLSchema, b: DocumentNode | string | GraphQLSchema): boolean;
declare function compareSubgraphNames(name1: string, name2: string): boolean;
declare function wrapMergedTypeResolver<TContext extends Record<string, any>>(originalResolver: MergedTypeResolver<TContext>, typeName: string, onDelegationStageExecuteHooks: OnDelegationStageExecuteHook<TContext>[], log: Logger): MergedTypeResolver<TContext>;
declare function millisecondsToStr(milliseconds: number): string;
declare function getTransportEntryMapUsingFusionAndFederationDirectives(unifiedGraph: GraphQLSchema, transportEntryAdditions?: TransportEntryAdditions): Record<string, TransportEntry<Record<string, any>>>;
declare const restoreExtraDirectives: (schema: GraphQLSchema) => GraphQLSchema;
declare function getStitchingDirectivesTransformerForSubschema(): (subschemaConfig: SubschemaConfig) => SubschemaConfig;
declare function handleResolveToDirectives(typeDefsOpt: TypeSource, additionalTypeDefs: TypeSource, additionalResolvers: IResolvers[]): DocumentNode;
interface HandleFederationSupergraphResult extends UnifiedGraphHandlerResult {
getSubschema(subgraphName: string): SubschemaConfig;
subschemas: SubschemaConfig[];
}
declare const handleFederationSupergraph: ({ unifiedGraph, onSubgraphExecute, onDelegationPlanHooks, onDelegationStageExecuteHooks, onDelegateHooks, batchDelegateOptions, handleProgressiveOverride, additionalTypeDefs: additionalTypeDefsFromConfig, additionalResolvers: additionalResolversFromConfig, log: rootLog, }: UnifiedGraphHandlerOpts) => HandleFederationSupergraphResult;
interface HandleFederationSubschemaOpts {
subschemaConfig: SubschemaConfig & {
endpoint?: string;
};
unifiedGraphDirectives?: Record<string, any>;
realSubgraphNameMap?: Map<string, string>;
additionalTypeDefs: TypeSource[];
stitchingDirectivesTransformer: (subschemaConfig: SubschemaConfig) => SubschemaConfig;
onSubgraphExecute: ReturnType<typeof getOnSubgraphExecute>;
}
declare function handleFederationSubschema({ subschemaConfig, unifiedGraphDirectives, realSubgraphNameMap, additionalTypeDefs, stitchingDirectivesTransformer, onSubgraphExecute, }: HandleFederationSubschemaOpts): SubschemaConfig<any, any, any, Record<string, any>> & {
endpoint?: string;
};
type SdkRequester = (document: DocumentNode, variables?: any, operationContext?: any) => any;
declare function getExecutorForUnifiedGraph<TContext>(opts: UnifiedGraphManagerOptions<TContext>): ((execReq: ExecutionRequest) => MaybePromise<MaybeAsyncIterable<ExecutionResult$1<any, any>>>) & AsyncDisposable;
interface SdkRequesterOptions extends UnifiedGraphManagerOptions<any> {
dataLoaderOptions?: Parameters<typeof getBatchingExecutor>[2];
extensionsReducer?: (mergedExtensions: Record<string, any>, request: ExecutionRequest) => Record<string, any>;
onExecutionRequest?(request: ExecutionRequest): MaybePromise<ExecutionRequest>;
}
declare function getSdkRequesterForUnifiedGraph(opts: SdkRequesterOptions): SdkRequester;
declare function handleMaybePromiseMaybeAsyncIterableResult<T>(result: MaybePromise<MaybeAsyncIterable<ExecutionResult$1<T>>>): MaybePromise<MaybeAsyncIterable<T | null>>;
export { type BatchDelegateOptions, type HandleFederationSubschemaOpts, type HandleFederationSupergraphResult, type Instrumentation, type OnDelegationPlanDoneHook, type OnDelegationPlanDonePayload, type OnDelegationPlanHook, type OnDelegationPlanHookPayload, type OnDelegationStageExecuteDoneHook, type OnDelegationStageExecuteDonePayload, type OnDelegationStageExecuteHook, type OnDelegationStageExecutePayload, type OnSubgraphExecuteDoneHook, type OnSubgraphExecuteDoneOnNextPayload, type OnSubgraphExecuteDonePayload, type OnSubgraphExecuteDoneResult, type OnSubgraphExecuteDoneResultOnEnd, type OnSubgraphExecuteDoneResultOnNext, type OnSubgraphExecuteHook, type OnSubgraphExecutePayload, type SchemaGenerationLease, type SdkRequesterOptions, type TransportEntryAdditions, type Transports, type UnifiedGraphHandler, type UnifiedGraphHandlerOpts, type UnifiedGraphHandlerResult, UnifiedGraphManager, type UnifiedGraphManagerOptions, type UnifiedGraphPlugin, type WrapExecuteWithHooksOptions, compareSchemas, compareSubgraphNames, ensureSchema, getExecutorForUnifiedGraph, getOnSubgraphExecute, getSdkRequesterForUnifiedGraph, getStitchingDirectivesTransformerForSubschema, getTransportEntryMapUsingFusionAndFederationDirectives, handleFederationSubschema, handleFederationSupergraph, handleMaybePromiseMaybeAsyncIterableResult, handleResolveToDirectives, millisecondsToStr, restoreExtraDirectives, wrapExecutorWithHooks, wrapMergedTypeResolver };