@graphql-hive/gateway-runtime
Version:
2,879 lines • 101 kB
JavaScript
import { useDisableIntrospection } from '@envelop/disable-introspection';
import { useGenericAuth } from '@envelop/generic-auth';
import { createSchemaFetcher, createSupergraphSDLFetcher } from '@graphql-hive/core';
import { millisecondsToStr, getOnSubgraphExecute, subgraphNameByExecutionRequest, UnifiedGraphManager, restoreExtraDirectives, getTransportEntryMapUsingFusionAndFederationDirectives, getStitchingDirectivesTransformerForSubschema, handleFederationSubschema, handleResolveToDirectives } from '@graphql-mesh/fusion-runtime';
export { getExecutorForUnifiedGraph, getSdkRequesterForUnifiedGraph } from '@graphql-mesh/fusion-runtime';
import { useHmacUpstreamSignature } from '@graphql-mesh/hmac-upstream-signature';
export * from '@graphql-mesh/hmac-upstream-signature';
import useMeshResponseCache from '@graphql-mesh/plugin-response-cache';
import { DefaultLogger, LogLevel, isUrl, readFileOrUrl, defaultImportFn, requestIdByRequest, getHeadersObj, wrapFetchWithHooks, isDisposable, dispose, getInContextSDK, pathExists } from '@graphql-mesh/utils';
export { DefaultLogger, LogLevel } from '@graphql-mesh/utils';
import { batchDelegateToSchema } from '@graphql-tools/batch-delegate';
import { getTypeInfo, EMPTY_OBJECT, delegateToSchema } from '@graphql-tools/delegate';
import { defaultPrintFn as defaultPrintFn$1 } from '@graphql-tools/executor-common';
import { getDirectiveExtensions, memoize1, isValidPath, pathToArray, memoize3, getDirective, createGraphQLError, isAsyncIterable, mapAsyncIterator, createDeferred, isPromise, isDocumentNode, asArray, printSchemaWithDirectives, parseSelectionSet, mergeDeep } from '@graphql-tools/utils';
import { wrapSchema, schemaFromExecutor } from '@graphql-tools/wrap';
import { useCSRFPrevention } from '@graphql-yoga/plugin-csrf-prevention';
import { useDeferStream } from '@graphql-yoga/plugin-defer-stream';
import { usePersistedOperations } from '@graphql-yoga/plugin-persisted-operations';
import { AsyncDisposableStack } from '@whatwg-node/disposablestack';
export * from '@whatwg-node/disposablestack';
import { handleMaybePromise, iterateAsync } from '@whatwg-node/promise-helpers';
import { useCookies } from '@whatwg-node/server-plugin-cookies';
import { print, visit, visitWithTypeInfo, getArgumentValues, getNamedType, isIntrospectionType, isListType, isCompositeType, getOperationAST, isSchema, parse, buildASTSchema, buildSchema } from 'graphql';
import { isAsyncIterable as isAsyncIterable$1, useReadinessCheck, useExecutionCancellation, createYoga, chain, mergeSchemas } from 'graphql-yoga';
import { process as process$1, path, fs } from '@graphql-mesh/cross-helpers';
import { DEFAULT_UPLINKS, fetchSupergraphSdlFromManagedFederation } from '@graphql-tools/federation';
import { JSONLogger } from '@graphql-hive/logger-json';
export { JSONLogger } from '@graphql-hive/logger-json';
import { useApolloUsageReport } from '@graphql-yoga/plugin-apollo-usage-report';
import { useHive } from '@graphql-hive/yoga';
import { useContentEncoding as useContentEncoding$1 } from '@whatwg-node/server';
import { defaultPrintFn } from '@graphql-mesh/transport-common';
import { abortSignalAny } from '@graphql-hive/signal';
import { isOriginalGraphQLError } from '@envelop/core';
function checkIfDataSatisfiesSelectionSet(selectionSet, data) {
if (Array.isArray(data)) {
return data.every(
(item) => checkIfDataSatisfiesSelectionSet(selectionSet, item)
);
}
for (const selection of selectionSet.selections) {
if (selection.kind === "Field") {
const field = selection;
const responseKey = field.alias?.value || field.name.value;
if (data[responseKey] != null) {
if (field.selectionSet) {
if (!checkIfDataSatisfiesSelectionSet(
field.selectionSet,
data[field.name.value]
)) {
return false;
}
}
} else {
return false;
}
} else if (selection.kind === "InlineFragment") {
const inlineFragment = selection;
if (!checkIfDataSatisfiesSelectionSet(inlineFragment.selectionSet, data)) {
return false;
}
}
}
return true;
}
const defaultQueryText = (
/* GraphQL */
`
# Welcome to GraphiQL
# GraphiQL is an in-browser tool for writing, validating,
# and testing GraphQL queries.
#
# Type queries into this side of the screen, and you will
# see intelligent typeaheads aware of the current GraphQL
# type schema and live syntax and validation errors
# highlighted within the text.
#
# GraphQL queries typically start with a "{" character.
# Lines that start with a # are ignored.
#
# An example GraphQL query might look like:
#
# {
# field(arg: "value") {
# subField
# }
# }
#
`
);
function delayInMs(ms, signal) {
return new Promise((resolve, reject) => {
globalThis.setTimeout(resolve, ms);
});
}
const getExecuteFnFromExecutor = memoize1(
function getExecuteFnFromExecutor2(executor) {
return function executeFn(args) {
return executor({
document: args.document,
variables: args.variableValues,
operationName: args.operationName ?? void 0,
rootValue: args.rootValue,
context: args.contextValue,
signal: args.signal
});
};
}
);
function wrapCacheWithHooks({
cache,
onCacheGet,
onCacheSet,
onCacheDelete
}) {
return new Proxy(cache, {
get(target, prop, receiver) {
switch (prop) {
case "get": {
if (onCacheGet.length === 0) {
break;
}
return function cacheGet(key) {
const onCacheGetResults = [];
return handleMaybePromise(
() => iterateAsync(
onCacheGet,
(onCacheGet2) => onCacheGet2({
key,
cache
}),
onCacheGetResults
),
() => handleMaybePromise(
() => target.get(key),
(value) => value == null ? handleMaybePromise(
() => iterateAsync(
onCacheGetResults,
(onCacheGetResult) => onCacheGetResult?.onCacheMiss?.()
),
() => value
) : handleMaybePromise(
() => iterateAsync(
onCacheGetResults,
(onCacheGetResult) => onCacheGetResult?.onCacheHit?.({ value })
),
() => value
),
(error) => handleMaybePromise(
() => iterateAsync(
onCacheGetResults,
(onCacheGetResult) => onCacheGetResult?.onCacheGetError?.({ error })
),
() => {
throw error;
}
)
)
);
};
}
case "set": {
if (onCacheSet.length === 0) {
break;
}
return function cacheSet(key, value, opts) {
const onCacheSetResults = [];
return handleMaybePromise(
() => iterateAsync(
onCacheSet,
(onCacheSet2) => onCacheSet2({
key,
value,
ttl: opts?.ttl,
cache
}),
onCacheSetResults
),
() => handleMaybePromise(
() => target.set(key, value, opts),
(result) => handleMaybePromise(
() => iterateAsync(
onCacheSetResults,
(onCacheSetResult) => onCacheSetResult?.onCacheSetDone?.()
),
() => result
),
(err) => handleMaybePromise(
() => iterateAsync(
onCacheSetResults,
(onCacheSetResult) => onCacheSetResult?.onCacheSetError?.({ error: err })
),
() => {
throw err;
}
)
)
);
};
}
case "delete": {
if (onCacheDelete.length === 0) {
break;
}
return function cacheDelete(key) {
const onCacheDeleteResults = [];
return handleMaybePromise(
() => iterateAsync(
onCacheDelete,
(onCacheDelete2) => onCacheDelete2({
key,
cache
})
),
() => handleMaybePromise(
() => target.delete(key),
(result) => handleMaybePromise(
() => iterateAsync(
onCacheDeleteResults,
(onCacheDeleteResult) => onCacheDeleteResult?.onCacheDeleteDone?.()
),
() => result
)
),
(err) => handleMaybePromise(
() => iterateAsync(
onCacheDeleteResults,
(onCacheDeleteResult) => onCacheDeleteResult?.onCacheDeleteError?.({ error: err })
),
() => {
throw err;
}
)
);
};
}
}
return Reflect.get(target, prop, receiver);
}
});
}
function urlMatches(url, specUrl) {
{
return url === specUrl;
}
}
function normalizeDirectiveName(directiveName) {
if (directiveName.startsWith("@")) {
return directiveName.slice(1);
}
return directiveName;
}
function getDirectiveNameForFederationDirective({
schema,
directiveName,
specUrl
}) {
const directivesOnSchemaDef = getDirectiveExtensions(schema, schema);
const normalizedDirectiveName = normalizeDirectiveName(directiveName);
if (directivesOnSchemaDef?.["link"]) {
const linkDirectives = directivesOnSchemaDef["link"];
for (const linkDirective of linkDirectives) {
if (urlMatches(linkDirective.url, specUrl)) {
const imports = linkDirective.import;
if (imports) {
for (const importDirective of imports) {
if (typeof importDirective === "string") {
const normalizedImportDirective = normalizeDirectiveName(importDirective);
if (normalizedImportDirective === normalizedDirectiveName) {
return normalizedImportDirective;
}
} else {
const normalizedImportDirective = normalizeDirectiveName(
importDirective.name
);
if (normalizedImportDirective === normalizedDirectiveName) {
const normalizedAlias = normalizeDirectiveName(
importDirective.as
);
return normalizedAlias;
}
}
}
}
}
}
}
return normalizedDirectiveName;
}
const defaultLoadedPlacePrefix = "GraphOS Managed Federation";
function decideMaxRetries({
graphosOpts,
pollingInterval,
minDelaySeconds,
uplinks,
initialSchemaExists
}) {
let maxRetries = graphosOpts.maxRetries || Math.max(3, uplinks.length);
if (initialSchemaExists && pollingInterval && pollingInterval <= minDelaySeconds * 1e3) {
maxRetries = 1;
}
return maxRetries;
}
function createGraphOSFetcher({
graphosOpts,
configContext,
pollingInterval
}) {
let lastSeenId;
let lastSupergraphSdl;
let nextFetchTime;
const uplinksParam = graphosOpts.upLink || process$1.env["APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT"];
const uplinks = uplinksParam?.split(",").map((uplink) => uplink.trim()) || DEFAULT_UPLINKS;
const graphosLogger = configContext.logger.child({ source: "GraphOS" });
graphosLogger.info("Using GraphOS with uplinks ", ...uplinks);
let supergraphLoadedPlace = defaultLoadedPlacePrefix;
if (graphosOpts.graphRef) {
supergraphLoadedPlace += ` <br>${graphosOpts.graphRef}`;
}
let minDelaySeconds = 10;
const uplinksToUse = [];
return {
supergraphLoadedPlace,
unifiedGraphFetcher(transportContext) {
const maxRetries = decideMaxRetries({
graphosOpts,
pollingInterval,
minDelaySeconds,
uplinks,
initialSchemaExists: !!lastSupergraphSdl
});
let retries = maxRetries;
function fetchSupergraphWithDelay() {
if (nextFetchTime) {
const currentTime = Date.now();
if (nextFetchTime >= currentTime) {
const delay = nextFetchTime - currentTime;
graphosLogger.info(
`Fetching supergraph with delay: ${millisecondsToStr(delay)}`
);
nextFetchTime = 0;
return delayInMs(delay).then(fetchSupergraph);
}
}
return fetchSupergraph();
}
function fetchSupergraph() {
if (uplinksToUse.length === 0) {
uplinksToUse.push(...uplinks);
}
retries--;
const uplinkToUse = uplinksToUse.pop();
const attemptMetadata = {
uplink: uplinkToUse || "none"
};
if (maxRetries > 1) {
attemptMetadata["attempt"] = `${maxRetries - retries}/${maxRetries}`;
}
const attemptLogger = graphosLogger.child(attemptMetadata);
attemptLogger.debug(`Fetching supergraph`);
return handleMaybePromise(
() => fetchSupergraphSdlFromManagedFederation({
graphRef: graphosOpts.graphRef,
apiKey: graphosOpts.apiKey,
upLink: uplinkToUse,
lastSeenId,
fetch: transportContext.fetch || configContext.fetch,
loggerByMessageLevel: {
ERROR(message) {
attemptLogger.error(message);
},
INFO(message) {
attemptLogger.info(message);
},
WARN(message) {
attemptLogger.warn(message);
}
}
}),
(result) => {
if (result.minDelaySeconds) {
minDelaySeconds = result.minDelaySeconds;
attemptLogger.debug(`Setting min delay to ${minDelaySeconds}s`);
}
nextFetchTime = Date.now() + minDelaySeconds * 1e3;
if ("error" in result && result.error) {
attemptLogger.error(result.error.code, result.error.message);
}
if ("id" in result) {
if (lastSeenId === result.id) {
attemptLogger.debug("Supergraph is unchanged");
return lastSupergraphSdl;
}
lastSeenId = result.id;
}
if ("supergraphSdl" in result && result.supergraphSdl) {
attemptLogger.debug(
`Fetched the new supergraph ${lastSeenId ? `with id ${lastSeenId}` : ""}`
);
lastSupergraphSdl = result.supergraphSdl;
}
if (!lastSupergraphSdl) {
if (retries > 0) {
return fetchSupergraphWithDelay();
}
throw new Error(
`Failed to fetch supergraph SDL from '${uplinkToUse}': [${JSON.stringify(result)}]`
);
}
return lastSupergraphSdl;
},
(err) => {
nextFetchTime = Date.now() + minDelaySeconds * 1e3;
if (retries > 0) {
attemptLogger.error(err);
return fetchSupergraphWithDelay();
}
if (lastSupergraphSdl) {
attemptLogger.error(err);
return lastSupergraphSdl;
}
if (err?.name === "TimeoutError") {
throw new Error(`HTTP request to '${uplinkToUse}' timed out`);
}
throw err;
}
);
}
return fetchSupergraphWithDelay();
}
};
}
function getDefaultLogger(opts) {
const logFormat = process$1.env["LOG_FORMAT"] || globalThis.LOG_FORMAT;
if (logFormat) {
if (logFormat.toLowerCase() === "json") {
return new JSONLogger(opts);
} else if (logFormat.toLowerCase() === "pretty") {
return new DefaultLogger(opts?.name, opts?.level);
}
}
const nodeEnv = process$1.env["NODE_ENV"] || globalThis.NODE_ENV;
if (nodeEnv === "production") {
return new JSONLogger(opts);
}
return new DefaultLogger(opts?.name, opts?.level);
}
function handleLoggingConfig(loggingConfig, existingLogger) {
if (typeof loggingConfig === "object") {
return loggingConfig;
}
if (typeof loggingConfig === "boolean") {
if (!loggingConfig) {
if (existingLogger && "logLevel" in existingLogger) {
existingLogger.logLevel = LogLevel.silent;
return existingLogger;
}
return getDefaultLogger({
name: existingLogger?.name,
level: LogLevel.silent
});
}
}
if (typeof loggingConfig === "number") {
if (existingLogger && "logLevel" in existingLogger) {
existingLogger.logLevel = loggingConfig;
return existingLogger;
}
return getDefaultLogger({
name: existingLogger?.name,
level: loggingConfig
});
}
if (typeof loggingConfig === "string") {
if (existingLogger && "logLevel" in existingLogger) {
existingLogger.logLevel = LogLevel[loggingConfig];
return existingLogger;
}
return getDefaultLogger({
name: existingLogger?.name,
level: LogLevel[loggingConfig]
});
}
return existingLogger || getDefaultLogger();
}
function getProxyExecutor({
config,
configContext,
getSchema,
onSubgraphExecuteHooks,
transportExecutorStack,
instrumentation
}) {
const fakeTransportEntryMap = {};
let subgraphName = "upstream";
const onSubgraphExecute = getOnSubgraphExecute({
onSubgraphExecuteHooks,
transportEntryMap: new Proxy(fakeTransportEntryMap, {
get(fakeTransportEntryMap2, subgraphNameProp) {
if (!fakeTransportEntryMap2[subgraphNameProp]) {
subgraphName = subgraphNameProp;
fakeTransportEntryMap2[subgraphNameProp] = {
kind: "http",
subgraph: subgraphName.toString(),
location: config.proxy?.endpoint,
headers: config.proxy?.headers,
options: config.proxy
};
}
return fakeTransportEntryMap2[subgraphNameProp];
}
}),
transportContext: configContext,
getSubgraphSchema: getSchema,
transportExecutorStack,
transports: config.transports,
instrumentation
});
return function proxyExecutor(executionRequest) {
return onSubgraphExecute(subgraphName, executionRequest);
};
}
function useHiveConsole({
enabled,
token,
...options
}) {
const agent = {
name: "hive-gateway",
logger: options.logger,
...options.agent
};
let usage = void 0;
if (options.usage && typeof options.usage === "object") {
usage = {
...options.usage,
clientInfo: typeof options.usage.clientInfo === "object" ? () => (
// @ts-expect-error clientInfo will be an object
options.usage.clientInfo
) : options.usage.clientInfo
};
} else {
usage = options.usage;
}
if (enabled && !token) {
throw new Error("Hive plugin is enabled but the token is not provided");
}
return useHive({
debug: ["1", "y", "yes", "t", "true"].includes(
String(process$1.env["DEBUG"])
),
...options,
enabled: !!enabled,
token,
agent,
usage
});
}
function getReportingPlugin(config, configContext) {
if (config.reporting?.type === "hive") {
const { target, ...reporting } = config.reporting;
let usage = reporting.usage;
if (usage === false) ; else {
usage = {
target,
...typeof usage === "object" ? { ...usage } : {}
};
}
return {
name: "Hive",
plugin: useHiveConsole({
logger: configContext.logger.child({ reporting: "Hive" }),
enabled: true,
...reporting,
...usage ? { usage } : {},
...config.persistedDocuments && "type" in config.persistedDocuments && config.persistedDocuments?.type === "hive" ? {
experimental__persistedDocuments: {
cdn: {
endpoint: config.persistedDocuments.endpoint,
accessToken: config.persistedDocuments.token
},
allowArbitraryDocuments: !!config.persistedDocuments.allowArbitraryDocuments
}
} : {}
})
};
} else if (config.reporting?.type === "graphos" || !config.reporting && "supergraph" in config && typeof config.supergraph === "object" && "type" in config.supergraph && config.supergraph.type === "graphos") {
if ("supergraph" in config && typeof config.supergraph === "object" && "type" in config.supergraph && config.supergraph.type === "graphos") {
if (!config.reporting) {
config.reporting = {
type: "graphos",
apiKey: config.supergraph.apiKey,
graphRef: config.supergraph.graphRef
};
} else {
config.reporting.apiKey ||= config.supergraph.apiKey;
config.reporting.graphRef ||= config.supergraph.graphRef;
}
}
return {
name: "GraphOS",
// @ts-expect-error - TODO: Fix types
plugin: useApolloUsageReport({
agentVersion: `hive-gateway@${globalThis.__VERSION__}`,
...config.reporting
})
};
}
return {
plugin: {}
};
}
function handleUnifiedGraphConfig(config, configContext) {
return handleMaybePromise(
() => typeof config === "function" ? config(configContext) : config,
(schema) => handleUnifiedGraphSchema(schema, configContext)
);
}
function handleUnifiedGraphSchema(unifiedGraphSchema, configContext) {
if (typeof unifiedGraphSchema === "string" && (isValidPath(unifiedGraphSchema) || isUrl(unifiedGraphSchema))) {
return readFileOrUrl(unifiedGraphSchema, {
fetch: configContext.fetch,
cwd: configContext.cwd,
logger: configContext.logger,
allowUnknownExtensions: true,
importFn: defaultImportFn
});
}
return unifiedGraphSchema;
}
var landingPageHtml = `<!doctype html><html lang=en><head><meta charset=utf-8><title>Welcome to __PRODUCT_NAME__</title><link rel=apple-touch-icon sizes=180x180 href=https://the-guild.dev/apple-touch-icon.png><link rel=icon type=image/png sizes=32x32 href=https://the-guild.dev/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=https://the-guild.dev/favicon-16x16.png><link rel=icon type=image/png sizes=16x16 href=https://the-guild.dev/favicon-16x16.png><link rel="shorcut icon" type=image/x-icon href=https://the-guild.dev/favicon.ico><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css><script src=https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js><\/script><script src=https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/typescript.min.js><\/script><script>hljs.highlightAll()<\/script><style>*{box-sizing:border-box}body,html{padding:20px;margin:0;background-color:#000;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol'}a{color:#add8e6}code{background-color:#d3d3d3;color:#000;padding:2px;border-radius:4px;font-family:monospace}table{border:2px solid #d3d3d3;border-radius:4px;border-spacing:0}th{text-align:left;color:#d3d3d3;border-bottom:4px solid #d3d3d3}td,th{padding:6px 8px}td{overflow-wrap:anywhere}pre{max-width:100%}.hero{display:flex;flex-direction:column;align-items:center}.hero .logo{font-size:2rem;display:flex;align-items:center;gap:10px}.hero .logo svg{height:50px}.hero .logo h1{margin:0}.hero .description{color:gray}.hero .links{text-align:center}.status{position:relative;margin:50px auto;display:flex;flex-direction:column;max-width:800px}.status h2,.status p{margin:0}.status .var{display:flex;flex-direction:column;align-items:flex-start}.status .var label{font-size:small}.four-oh-four{position:relative;display:flex;flex-direction:column;align-items:center;background-color:#2f2f2f;border-radius:4px;margin:0 auto;padding:10px;max-width:800px;border:1px solid gray;opacity:.4;transition:opacity .3s ease-in-out}.four-oh-four:hover{opacity:1}.four-oh-four p{text-align:center}</style></head><body id=body><main><section class=hero><div class=logo><!-- will be an <svg> --> __PRODUCT_LOGO__<h1>__PRODUCT_NAME__</h1></div><p class=description>__PRODUCT_DESCRIPTION__</p><div class=links><a href=__PRODUCT_LINK__ class=docs>\u{1F4DA} Read the Documentation</a><br><a href=__GRAPHIQL_LINK__ class=graphiql>\u{1F5C3}\uFE0F Visit GraphiQL</a></div></section><section class=status>__SUBGRAPH_HTML__</section><section class=four-oh-four><h2>\u2139\uFE0F Not the Page You Expected To See?</h2><p>This page is shown be default whenever a 404 is hit.<br>You can disable this by behavior via the <code>landingPage</code> option.</p><pre><code class=language-typescript>import { defineConfig } from '__PRODUCT_PACKAGE_NAME__';
export const gatewayConfig = defineConfig({
landingPage: false,
});
</code></pre><p>If you expected <u>this</u> page to be the GraphQL route, you need to configure Hive Gateway.<br>Currently, the GraphQL route is configured to be on <a href=__GRAPHIQL_LINK__ class=graphiql>__GRAPHIQL_LINK__</a>.</p><pre><code class=language-typescript>import { defineConfig } from '__PRODUCT_PACKAGE_NAME__';
export const gatewayConfig = defineConfig({
graphqlEndpoint: '__REQUEST_PATH__',
});
</code></pre></section></main></body></html>`;
function useCacheDebug(opts) {
return {
onCacheGet({ key }) {
return {
onCacheGetError({ error }) {
const cacheGetErrorLogger = opts.logger.child("cache-get-error");
cacheGetErrorLogger.error({ key, error });
},
onCacheHit({ value }) {
const cacheHitLogger = opts.logger.child("cache-hit");
cacheHitLogger.debug({ key, value });
},
onCacheMiss() {
const cacheMissLogger = opts.logger.child("cache-miss");
cacheMissLogger.debug({ key });
}
};
},
onCacheSet({ key, value, ttl }) {
return {
onCacheSetError({ error }) {
const cacheSetErrorLogger = opts.logger.child("cache-set-error");
cacheSetErrorLogger.error({ key, value, ttl, error });
},
onCacheSetDone() {
const cacheSetDoneLogger = opts.logger.child("cache-set-done");
cacheSetDoneLogger.debug({ key, value, ttl });
}
};
},
onCacheDelete({ key }) {
return {
onCacheDeleteError({ error }) {
const cacheDeleteErrorLogger = opts.logger.child("cache-delete-error");
cacheDeleteErrorLogger.error({ key, error });
},
onCacheDeleteDone() {
const cacheDeleteDoneLogger = opts.logger.child("cache-delete-done");
cacheDeleteDoneLogger.debug({ key });
}
};
}
};
}
function useContentEncoding({
subgraphs
} = {}) {
if (!subgraphs?.length) {
return useContentEncoding$1();
}
const compressionAlgorithm = "gzip";
let fetchAPI;
const execReqWithContentEncoding = /* @__PURE__ */ new WeakSet();
return {
onYogaInit({ yoga }) {
fetchAPI = yoga.fetchAPI;
},
onPluginInit({ addPlugin }) {
addPlugin(
// @ts-expect-error - Plugin types do not match
useContentEncoding$1()
);
},
onSubgraphExecute({ subgraphName, executionRequest }) {
if (subgraphs.includes(subgraphName) || subgraphs.includes("*")) {
execReqWithContentEncoding.add(executionRequest);
}
},
onFetch({ executionRequest, options, setOptions }) {
if (options.body && !options.headers?.["Content-Encoding"] && executionRequest && execReqWithContentEncoding.has(executionRequest) && fetchAPI.CompressionStream) {
const compressionStream = new fetchAPI.CompressionStream(
compressionAlgorithm
);
let bodyStream;
if (options.body instanceof fetchAPI.ReadableStream) {
bodyStream = options.body;
} else {
bodyStream = new fetchAPI.Response(options.body).body;
}
setOptions({
...options,
headers: {
"Accept-Encoding": "gzip, deflate",
...options.headers,
"Content-Encoding": compressionAlgorithm
},
body: bodyStream.pipeThrough(compressionStream)
});
}
}
};
}
function useCustomAgent(agentFactory) {
return {
onFetch(payload) {
const agent = agentFactory(payload);
if (agent != null) {
payload.setOptions({
...payload.options,
// @ts-expect-error - `agent` is there
agent
});
}
}
};
}
function useDelegationPlanDebug(opts) {
let fetchAPI;
const stageExecuteLogById = /* @__PURE__ */ new WeakMap();
return {
onYogaInit({ yoga }) {
fetchAPI = yoga.fetchAPI;
},
onDelegationPlan({
typeName,
variables,
fragments,
fieldNodes,
info,
logger = opts.logger
}) {
const planId = fetchAPI.crypto.randomUUID();
const planLogger = logger.child({ planId, typeName });
const delegationPlanStartLogger = planLogger.child(
"delegation-plan-start"
);
delegationPlanStartLogger.debug(() => {
const logObj = {};
if (variables && Object.keys(variables).length) {
logObj["variables"] = variables;
}
if (fragments && Object.keys(fragments).length) {
logObj["fragments"] = Object.fromEntries(
Object.entries(fragments).map(([name, fragment]) => [
name,
print(fragment)
])
);
}
if (fieldNodes && fieldNodes.length) {
logObj["fieldNodes"] = fieldNodes.map(
(fieldNode) => print(fieldNode)
);
}
if (info?.path) {
logObj["path"] = pathToArray(info.path).join(" | ");
}
return logObj;
});
return ({ delegationPlan }) => {
const delegationPlanDoneLogger = logger.child("delegation-plan-done");
delegationPlanDoneLogger.debug(
() => delegationPlan.map((plan) => {
const planObj = {};
for (const [subschema, selectionSet] of plan) {
if (subschema.name) {
planObj[subschema.name] = print(selectionSet);
}
}
return planObj;
})
);
};
},
onDelegationStageExecute({
object,
info,
context,
subgraph,
selectionSet,
key,
typeName,
logger = opts.logger
}) {
let contextLog = stageExecuteLogById.get(context);
if (!contextLog) {
contextLog = /* @__PURE__ */ new Set();
stageExecuteLogById.set(context, contextLog);
}
const log = {
key: JSON.stringify(key),
object: JSON.stringify(object),
selectionSet: print(selectionSet)
};
const logStr = JSON.stringify(log);
if (contextLog.has(logStr)) {
return;
}
contextLog.add(logStr);
const logMeta = {
stageId: fetchAPI.crypto.randomUUID(),
subgraph,
typeName
};
const delegationStageLogger = logger.child(logMeta);
delegationStageLogger.debug("delegation-plan-start", () => {
return {
...log,
path: pathToArray(info.path).join(" | ")
};
});
return ({ result }) => {
const delegationStageExecuteDoneLogger = logger.child(
"delegation-stage-execute-done"
);
delegationStageExecuteDoneLogger.debug(() => result);
};
}
};
}
function getDepthOfListType(type) {
let depth = 0;
while (isListType(type)) {
depth++;
type = type.ofType;
}
return depth;
}
const getCostListSizeDirectiveNames = memoize1(
function getCostListSizeDirectiveNames2(schema) {
const costDirectiveName = getDirectiveNameForFederationDirective({
schema,
directiveName: "cost",
specUrl: "https://specs.apollo.dev/cost/v0.1"
});
const listSizeDirectiveName = getDirectiveNameForFederationDirective({
schema,
directiveName: "listSize",
specUrl: "https://specs.apollo.dev/cost/v0.1"
});
return {
cost: costDirectiveName,
listSize: listSizeDirectiveName
};
}
);
function createCalculateCost({
listSize,
operationTypeCost,
typeCost,
fieldCost
}) {
return memoize3(function calculateCost(schema, document, variables) {
const { cost: costDirectiveName, listSize: listSizeDirectiveName } = getCostListSizeDirectiveNames(schema);
let cost = 0;
const factorQueue = [];
function timesFactor(c) {
for (const f of factorQueue) {
c *= f;
}
return c;
}
const fieldFactorMap = /* @__PURE__ */ new Map();
const typeInfo = getTypeInfo(schema);
visit(
document,
visitWithTypeInfo(typeInfo, {
OperationTypeDefinition(node) {
cost += operationTypeCost(node.operation) || 0;
},
Field: {
enter(node) {
let currentFieldCost = 0;
const field = typeInfo.getFieldDef();
if (field) {
const fieldAnnotations = getDirectiveExtensions(field, schema);
const factoryResult = fieldCost?.(node, typeInfo);
if (factoryResult) {
currentFieldCost += factoryResult;
} else if (fieldAnnotations?.[costDirectiveName]) {
for (const costAnnotation of fieldAnnotations[costDirectiveName]) {
if (costAnnotation?.weight) {
const weight = Number(costAnnotation.weight);
if (weight && !isNaN(weight)) {
currentFieldCost += weight;
}
}
}
}
const returnType = typeInfo.getType();
let factor = 1;
const sizedFieldFactor = fieldFactorMap.get(node.name.value);
if (sizedFieldFactor) {
factor = sizedFieldFactor;
fieldFactorMap.delete(field.name);
} else if (fieldAnnotations?.[listSizeDirectiveName]) {
for (const listSizeAnnotation of fieldAnnotations[listSizeDirectiveName]) {
if (listSizeAnnotation) {
if ("slicingArguments" in listSizeAnnotation) {
const slicingArguments = listSizeAnnotation.slicingArguments;
const argValues = getArgumentValues(
field,
node,
variables
);
let factorSet = false;
let slicingArgumentFactor = 1;
for (const slicingArgument of slicingArguments) {
const value = argValues[slicingArgument];
const numValue = Number(value);
if (numValue && !isNaN(numValue)) {
slicingArgumentFactor = Math.max(
slicingArgumentFactor,
numValue
);
if (factorSet && listSizeAnnotation.requireOneSlicingArgument !== false) {
throw createGraphQLError(
`Only one slicing argument is allowed on field "${field.name}"; found multiple slicing arguments "${slicingArguments.join(", ")}"`,
{
extensions: {
code: "COST_QUERY_PARSE_FAILURE"
}
}
);
}
factorSet = true;
}
}
if (listSizeAnnotation.sizedFields?.length) {
for (const sizedField of listSizeAnnotation.sizedFields) {
fieldFactorMap.set(sizedField, slicingArgumentFactor);
}
} else {
factor = slicingArgumentFactor;
}
} else if ("assumedSize" in listSizeAnnotation) {
const assumedSizeVal = listSizeAnnotation.assumedSize;
const numValue = Number(assumedSizeVal);
if (numValue && !isNaN(numValue)) {
factor = numValue;
}
}
}
}
} else if (listSize && returnType) {
const depth = getDepthOfListType(returnType);
if (depth > 0) {
factor = listSize * depth;
}
}
factorQueue.push(factor);
if (returnType) {
const namedReturnType = getNamedType(returnType);
if (isIntrospectionType(namedReturnType)) {
return;
}
const namedReturnTypeAnnotations = getDirectiveExtensions(
namedReturnType,
schema
);
if (namedReturnTypeAnnotations?.[costDirectiveName]) {
for (const costAnnotation of namedReturnTypeAnnotations[costDirectiveName]) {
if (costAnnotation?.weight) {
const weight = Number(costAnnotation?.weight);
if (weight && !isNaN(weight)) {
currentFieldCost += weight;
}
}
}
} else {
currentFieldCost += typeCost(namedReturnType);
}
}
if (currentFieldCost) {
cost += timesFactor(currentFieldCost);
}
}
},
leave() {
factorQueue.pop();
}
},
Directive() {
const directive = typeInfo.getDirective();
if (directive) {
const directiveCostAnnotations = getDirective(
schema,
directive,
"cost"
);
if (directiveCostAnnotations) {
for (const costAnnotation of directiveCostAnnotations) {
if (costAnnotation["weight"]) {
const weight = Number(costAnnotation["weight"]);
if (weight && !isNaN(weight)) {
cost += timesFactor(weight);
}
}
}
}
}
},
Argument() {
const argument = typeInfo.getArgument();
if (argument) {
const argumentCostAnnotations = getDirective(
schema,
argument,
"cost"
);
if (argumentCostAnnotations) {
for (const costAnnotation of argumentCostAnnotations) {
if (costAnnotation["weight"]) {
const weight = Number(costAnnotation["weight"]);
if (weight && !isNaN(weight)) {
cost += timesFactor(weight);
}
}
}
}
}
}
})
);
return cost;
});
}
function useDemandControl({
listSize = 0,
maxCost,
includeExtensionMetadata = process$1.env["NODE_ENV"] === "development",
operationTypeCost = (operationType) => operationType === "mutation" ? 10 : 0,
fieldCost,
typeCost = (type) => isCompositeType(type) ? 1 : 0
}) {
const calculateCost = createCalculateCost({
listSize,
operationTypeCost,
fieldCost,
typeCost
});
const costByContextMap = /* @__PURE__ */ new WeakMap();
return {
onSubgraphExecute({ subgraph, executionRequest, logger }) {
const demandControlLogger = logger?.child("demand-control");
let costByContext = executionRequest.context ? costByContextMap.get(executionRequest.context) || 0 : 0;
const operationCost = calculateCost(
subgraph,
executionRequest.document,
executionRequest.variables || EMPTY_OBJECT
);
costByContext += operationCost;
if (executionRequest.context) {
costByContextMap.set(executionRequest.context, costByContext);
}
demandControlLogger?.debug({
operationCost,
totalCost: costByContext
});
if (maxCost != null && costByContext > maxCost) {
throw createGraphQLError(
`Operation estimated cost ${costByContext} exceeded configured maximum ${maxCost}`,
{
extensions: {
code: "COST_ESTIMATED_TOO_EXPENSIVE",
cost: {
estimated: costByContext,
max: maxCost
}
}
}
);
}
},
onExecutionResult({ result, setResult, context }) {
if (includeExtensionMetadata) {
const costByContext = costByContextMap.get(context) || 0;
if (isAsyncIterable(result)) {
setResult(
mapAsyncIterator(result, (value) => ({
...value,
extensions: {
...value.extensions || {},
cost: {
estimated: costByContext,
max: maxCost
}
}
}))
);
} else {
setResult({
...result || {},
extensions: {
...result?.extensions || {},
cost: {
estimated: costByContext,
max: maxCost
}
}
});
}
}
}
};
}
function useFetchDebug(opts) {
let fetchAPI;
return {
onYogaInit({ yoga }) {
fetchAPI = yoga.fetchAPI;
},
onFetch({ url, options, logger = opts.logger }) {
const fetchId = fetchAPI.crypto.randomUUID();
const fetchLogger = logger.child({
fetchId
});
const httpFetchRequestLogger = fetchLogger.child("http-fetch-request");
httpFetchRequestLogger.debug(() => ({
url,
...options || {},
body: options?.body,
headers: options?.headers,
signal: options?.signal?.aborted ? options?.signal?.reason : false
}));
const start = performance.now();
return function onFetchDone({ response }) {
const httpFetchResponseLogger = fetchLogger.child(
"http-fetch-response"
);
httpFetchResponseLogger.debug(() => ({
status: response.status,
headers: Object.fromEntries(response.headers.entries()),
duration: performance.now() - start
}));
};
}
};
}
function usePropagateHeaders(opts) {
const resHeadersByRequest = /* @__PURE__ */ new WeakMap();
return {
onFetch({ executionRequest, context, options, setOptions }) {
const request = context?.request || executionRequest?.context?.request;
if (request) {
const subgraphName = executionRequest && subgraphNameByExecutionRequest.get(executionRequest);
return handleMaybePromise(
() => handleMaybePromise(
() => opts.fromClientToSubgraphs?.({
request,
subgraphName
}),
(propagatingHeaders) => {
const headers = options.headers || {};
for (const key in propagatingHeaders) {
const value = propagatingHeaders[key];
if (value != null && headers[key] == null) {
headers[key] = value;
}
}
setOptions({
...options,
headers
});
}
),
() => {
if (opts.fromSubgraphsToClient) {
return function onFetchDone({ response }) {
return handleMaybePromise(
() => opts.fromSubgraphsToClient?.({
response,
subgraphName
}),
(headers) => {
if (headers && request) {
let existingHeaders = resHeadersByRequest.get(request);
if (!existingHeaders) {
existingHeaders = {};
resHeadersByRequest.set(request, existingHeaders);
}
for (const key in headers) {
const value = headers[key];
if (value != null) {
const headerAsArray = Array.isArray(value) ? value : [value];
if (existingHeaders[key]) {
existingHeaders[key].push(...headerAsArray);
} else {
existingHeaders[key] = headerAsArray;
}
}
}
}
}
);
};
}
}
);
}
},
onResponse({ response, request }) {
const headers = resHeadersByRequest.get(request);
if (headers) {
for (const key in headers) {
const value = headers[key];
if (value) {
for (const v of value) {
response.headers.append(key, v);
}
}
}
}
}
};
}
const defaultGenerateRequestId = ({
fetchAPI = globalThis
}) => fetchAPI.crypto.randomUUID();
const defaultRequestIdHeader = "x-request-id";
function useRequestId(opts) {
const headerName = opts?.headerName || defaultRequestIdHeader;
const generateRequestId = opts?.generateRequestId || defaultGenerateRequestId;
return {
onRequest({ request, fetchAPI, serverContext }) {
const requestId = request.headers.get(headerName) || generateRequestId({
request,
fetchAPI,
// @ts-expect-error - Server context is not typed
context: serverContext
});
requestIdByRequest.set(request, requestId);
},
onContextBuilding({ context }) {
if (context?.request) {
const requestId = requestIdByRequest.get(context.request);
if (requestId && context.logger) {
context.logger = context.logger.child({ requestId });
}
}
},
onFetch({ context, options, setOptions }) {
if (context?.request) {
const requestId = requestIdByRequest.get(context.request);
if (requestId) {
setOptions({
...options || {},
headers: {
...options.headers || {},
[headerName]: requestId
}
});
}
}
},
onResponse({ request, response }) {
const requestId = requestIdByRequest.get(request);
if (requestId) {
response.headers.set(headerName, requestId);
}
}
};
}
function useRetryOnSchemaReload({
logger
}) {
const execHandlerByContext = /* @__PURE__ */ new WeakMap();
function handleOnExecute(args) {
if (args.contextValue) {
const operation = getOperationAST(args.document, args.operationName);
if (operation?.operation !== "query") {
execHandlerByContext.delete(args.contextValue);
}
}
}
function handleExecutionResult({
context,
result,
setResult,
request
}) {
const execHandler = execHandlerByContext.get(context);
if (execHandler && result?.errors?.some((e) => e.extensions?.["code"] === "SCHEMA_RELOAD")) {
let requestLogger = logger;
const requestId = requestIdByRequest.get(request);
if (requestId) {
requestLogger = logger.child({ requestId });
}
requestLogger.info(
"The operation has been aborted after the supergraph schema reloaded, retrying the operation..."
);
if (execHandler) {
return handleMaybePromise(
execHandler,
(newResult) => setResult(newResult)
);
}
}
}
return {
onParams({ request, params, context, paramsHandler }) {
execHandlerByContext.set(
context,
() => paramsHandler({
request,
params,
context
})
);
},
onExecute({ args }) {
handleOnExecute(args);
},
onSubscribe({ args }) {
handleOnExecute(args);
},
onExecutionResult({ request, context, result, setResult }) {
if (isAsyncIterable$1(result)) {
return;
}
return handleExecutionResult({ context, result, setResult, request });
},
onResultProcess({ result, setResult, serverContext, request }) {
if (isAsyncIterable$1(result) || Array.isArray(result)) {
return;
}
return handleExecutionResult({
context: serverContext,
result,
setResult,
request
});
}
};
}
function useSubgraphErrorPlugin({
errorCode = "DOWNSTREAM_SERVICE_ERROR",
subgraphNameProp = "serviceName"
} = {}) {
function extendError(error, subgraphName) {
const errorExtensions = error.extensions ||= {};
if (errorCode) {
errorExtensions.code ||= errorCode;
}
if (subgraphNameProp) {
errorExtensions[subgraphNameProp] ||= subgraphName;
}
}
return {
onSubgraphExecute({ subgraphName }) {
return function({ result }) {
if (isAsyncIterable(result)) {
return {
onNext({ result: result2 }) {
if (result2.errors) {
for (const error of result2.errors) {
extendError(error, subgraphName);
}
}
}
};
}
if (result.errors) {
for (const error of result.errors) {
extendError(error, subgraphName);
}
}
return;
};
}
};
}
function useSubgraphExecuteDebug(opts) {
let fetchAPI;
return {
onYogaInit({ yoga }) {
fetchAPI = yoga.fetchAPI;
},
onSubgraphExecute({ executionRequest, logger = opts.logger }) {
const subgraphExecuteHookLogger = logger.child({
subgraphExecuteId: fetchAPI.crypto.randomUUID()
});
const subgraphExecuteStartLogger = subgraphExecuteHookLogger.child(
"subgraph-execute-start"
);
subgraphExecuteStartLogger.debug(() => {
const logData = {};
if (executionRequest.document) {
logData["query"] = defaultPrintFn(executionRequest.document);
}
if (executionRequest.variables && Object.keys(executionRequest.variables).length) {
logData["variables"] = executionRequest.variables;
}
return logData;
});
const start = performance.now();
return function onSubgraphExecuteDone({ result }) {
const subgraphExecuteEndLogger = subgraphExecuteHookLogger.child(
"subgraph-execute-end"
);
if (isAsyncIterable$1(result)) {
return {
onNext({ result: result2 }) {
const subgraphExecuteNextLogger = subgraphExecuteHookLogger.child(
"subgraph-execute-next"
);
subgraphExecuteNextLogger.debug(result2);
},
onEnd() {
subgraphExecuteEndLogger.debug(() => ({
duration: performance.now() - start
}));
}
};
}
subgraphExecuteEndLogger.debug(result);
return void 0;
};
}
};
}
function useUpstreamCancel() {
return {
onFetch({ context, options, executionRequest, info }) {
const signals = [];
if (context?.request?.signal) {
signals.push(context.request.signal);
}
const execRequestSignal = executionRequest?.signal || executionRequest?.info?.signal;
if (execRequestSignal) {
signals.push(execRequestSignal);
}
const signalInInfo = info?.signal;
if (signalInInfo) {
signals.push(signalInInfo);
}
if (options.signal) {
signals.push(options.signal);
}
options.signal = abortSignalAny(signals);
},
onSubgraphExecute({ executionRequest }) {
const signals = [];
if (executionRequest.info?.signal) {
signals.push(executionRequest.info.signal);
}
if (executionRequest.context?.request?.signal) {
signals.push(executionRequest.context.request.signal);
}
if (executionRequest.signal) {
signals.push(executionRequest.signal);
}
executionRequest.signal = abortSignalAny(signals);
}
};
}
function useUpstreamRetry(opts) {
const timeouts = /* @__PURE__ */ new Set();
const retryOptions = typeof opts === "function" ? opts : () => opts;
const executionRequestResponseMap = /* @__PURE__ */ new WeakMap();
return {
onSubgraphExecute({
subgraphName,
executionRequest,
executor,
setExecutor
}) {
const optsForReq = retryOptions({ subgraphName, executionRequest });
if (optsForReq) {
const {
maxRetries,
retryDelay = 1e3,
retryDelayFactor = 1.25,
shouldRetry = ({ response, executionResult }) => {
if (response) {
if (response.status >= 500 || response.status === 429 || response.headers.get("Retry-After")) {
return true;
}
}
if (!executionResult || !isAsyncIterable(executionResult) && executionResult.errors?.length && executionResult.errors.some((e) => !isOriginalGraphQLError(e))) {
return true;
}
return false;
}
} = optsForReq;
if (maxRetries > 0) {
setExecutor(function(executionRequest2) {
let attemptsLeft = maxRetries + 1;
let executionResult;
let currRetryDelay = retryDelay;
function retry() {
try {
if (attemptsLeft <= 0) {
return executionResult;
}
const requestTime = Date.now();
attemptsLeft--;
return handleMaybePromise(
() => executor(executionRequest2),
(currRes) => {
executionResult = currRes;
let retryAfterSecondsFromHeader;
const response = executionRequestResponseMap.get(executionRequest2);
executionRequestResponseMap.delete(executionRequest2);
const retryAfterHeader = response?.headers.get("Retry-After");
if (retryAfterHeader) {
retryAfterSecondsFromHeader = parseInt(retryAfterHeader) * 1e3;
if (isNaN(retryAfterSecondsFromHeader)) {
const dateTime = new Date(retryAfterHeader).getTime();
if (!isNaN(dateTime)) {
retryAfterSecondsFromHeader = dateTime - requestTime;
}
}
}
currRetryDelay = retryAfterSecondsFromHeader || currRetryDelay * retryDelayFactor;
if (shouldRetry({
executionRequest: executionRequest2,
executionResult,
response
})) {
return new Promise((resolve) => {
const timeout = setTimeout(() => {
timeouts.delete(timeout);
resolve(retry());
}, currRetryDelay);
timeouts.add(timeout);
});
}
return executionResult;
},
(e) => {
if (attemptsLeft <= 0) {
throw e;
}
return retry();
}
);
} catch (e) {
if (attemptsLeft <= 0) {
throw e;
}
return retry();
}
}
return retry();
});
}
}
},
onFetch({ info, executionRequest }) {
executionRequest ||= info?.rootValue?.executionRequest;
if (executionRequest) {
return function onFetchDone({ response }) {
executionRequestResponseMap.set(executionRequest, response);
};
}
return void 0;
},
onDispose() {
for (const timeout of timeouts) {
clearTimeout(timeout);
timeouts.delete(timeout);
}
}
};
}
function useUpstreamTimeout(opts) {
const timeoutFactory = typeof opts === "function" ? opts : () => opts;
const timeoutSignalsByExecutionRequest = /* @__PURE__ */ new WeakMap();
const errorExtensionsByExecRequest = /* @__PURE__ */ new WeakMap();
return {
onSubgraphExecute({
subgraphName,
executionRequest,
executor,
setExecutor
}) {
const timeout = timeoutFactory({ subgraphName, executionRequest });
if (timeout) {
setExecutor(function timeoutExecutor(executionRequest2) {
let timeoutSignal = timeoutSignalsByExecutionRequest.get(executionRequest2);
if (!timeoutSignal) {
timeoutSignal = AbortSignal.timeout(timeout);
timeoutSignalsByExecutionRequest.set(
executionRequest2,
timeoutSignal
);
}
const signals = [];
signals.push(timeoutSignal);
if (executionRequest2.signal) {
signals.push(executionRequest2.signal);
}
const timeoutDeferred = createDeferred();
function rejectDeferred() {
timeoutDeferred.reject(timeoutSignal?.reason);
}
timeoutSignal.addEventListener("abort", rejectDeferred, {
once: true
});
const combinedSignal = abortSignalAny(signals);
const res$ = executor({
...executionRequest2,
signal: combinedSignal
});
if (!isPromise(res$)) {
return res$;
}
return Promise.race([timeoutDeferred.promise, res$]).then((result) => {
if (isAsyncIterable(result)) {
return {
[Symbol.asyncIterator]() {
const iterator = result[Symbol.asyncIterator]();
if (iterator.return) {
timeoutSignal.addEventListener(
"abort",
() => {
iterator.return?.(timeoutSignal.reason);
},
{
once: true
}
);
}
return iterator;
}
};
}
return result;
}).catch((e) => {
if (e === timeoutSignal.reason) {
const upstreamErrorExtensions = errorExtensionsByExecRequest.get(executionRequest2);
throw createGraphQLError(e.message, {
extensions: {
...upstreamErrorExtensions,
code: "TIMEOUT_ERROR",
http: {
status: 504
}
}
});
}
throw e;
}).finally(() => {
timeoutDeferred.resolve(void 0);
timeoutSignal.removeEventListener("abort", rejectDeferred);
errorExtensionsByExecRequest.delete(executionRequest2);
timeoutSignalsByExecutionRequest.delete(executionRequest2);
});
});
}
return void 0;
},
onFetch({ url, executionRequest, options, setOptions }) {
const subgraphName = executionRequest && subgraphNameByExecutionRequest.get(executionRequest);
if (!executionRequest || !timeoutSignalsByExecutionRequest.has(executionRequest)) {
const timeout = timeoutFactory({ subgraphName, executionRequest });
if (timeout) {
let timeoutSignal;
if (executionRequest) {
timeoutSignal = timeoutSignalsByExecutionRequest.get(executionRequest);
if (!timeoutSignal) {
timeoutSignal = AbortSignal.timeout(timeout);
timeoutSignalsByExecutionRequest.set(
executionRequest,
timeoutSignal
);
}
} else {
timeoutSignal = AbortSignal.timeout(timeout);
}
const signals = [];
signals.push(timeoutSignal);
if (options.signal) {
signals.push(options.signal);
}
setOptions({
...options,
signal: abortSignalAny(signals)
});
}
}
if (executionRequest) {
const upstreamErrorExtensions = {
serviceName: subgraphName,
request: {
url,
method: options.method,
body: options.body
}
};
errorExtensionsByExecRequest.set(
executionRequest,
upstreamErrorExtensions
);
return function onFetchDone({ response }) {
timeoutSignalsByExecutionRequest.delete(executionRequest);
upstreamErrorExtensions.response = {
status: response.status,
statusText: response.statusText,
headers: getHeadersObj(response.headers)
};
};
}
return void 0;
}
};
}
function useWebhooks({
pubsub,
logger
}) {
if (!pubsub) {
throw new Error(`You must provide a pubsub instance to webhooks feature!
Example:
import { PubSub } from '@graphql-hive/gateway'
export const gatewayConfig = defineConfig({
pubsub: new PubSub(),
webhooks: true,
})
See documentation: https://the-guild.dev/docs/mesh/pubsub`);
}
return {
onRequest({ request, url, endResponse, fetchAPI }) {
const eventNames = pubsub.getEventNames();
if (eventNames.length === 0) {
return;
}
const requestMethod = request.method.toLowerCase();
const pathname = url.pathname;
const expectedEventName = `webhook:${requestMethod}:${pathname}`;
for (const eventName of eventNames) {
if (eventName === expectedEventName) {
logger?.debug(() => `Received webhook request for ${pathname}`);
return handleMaybePromise(
() => request.text(),
function handleWebhookPayload(webhookPayload) {
logger?.debug(
() => `Emitted webhook request for ${pathname}: ${webhookPayload}`
);
webhookPayload = request.headers.get("content-type") === "application/json" ? JSON.parse(webhookPayload) : webhookPayload;
pubsub.publish(eventName, webhookPayload);
return endResponse(
new fetchAPI.Response(null, {
status: 204,
statusText: "OK"
})
);
}
);
}
}
}
};
}
const defaultProductLogo = (
/* HTML */
`<svg
viewBox="0 0 52 53"
fill="currentColor"
class="size-7 text-green-1000"
>
<defs>
<path
id="hive-gateway-path"
d="m25 .524872-7.7758.000001V13.6981c0 2.2382-1.8128 4.051-4.0509 4.051H0l7.2e-7 7.7758H8.48411c1.06096 0 2.07849-.4215 2.82859-1.1718l12.5159-12.5176C24.5786 11.0854 25 10.068 25 9.00727V.524872Zm2 0 7.7758.000001V13.6981c0 2.2382 1.8128 4.051 4.0509 4.051H52v7.7758h-8.4841c-1.061 0-2.0785-.4215-2.8286-1.1718L28.1714 11.8355C27.4214 11.0854 27 10.068 27 9.00727V.524872ZM25 52.5249h-7.7758V39.3516c0-2.2381-1.8128-4.0509-4.0509-4.0509H0l7.2e-7-7.7758H8.48411c1.06096 0 2.07849.4215 2.82859 1.1717l12.5159 12.5176c.75.7502 1.1714 1.7675 1.1714 2.8283v8.4824Zm2 0h7.7758V39.3516c0-2.2381 1.8128-4.0509 4.0509-4.0509H52v-7.7758h-8.4841c-1.061 0-2.0785.4215-2.8286 1.1717L28.1714 41.2142c-.75.7502-1.1714 1.7675-1.1714 2.8283v8.4824Zm2.8369-29.837H22.163v7.6739h7.6739v-7.6739Z"
></path>
<clipPath id="hive-gateway-clip-path">
<use href="#hive-gateway-path"></use>
</clipPath>
</defs>
<use href="#hive-gateway-path" clip-path="url(#hive-gateway-clip-path)"></use>
</svg>`
);
function createGatewayRuntime(config) {
let fetchAPI = config.fetchAPI;
const logger = handleLoggingConfig(config.logging);
let instrumentation;
const onFetchHooks = [];
const onCacheGetHooks = [];
const onCacheSetHooks = [];
const onCacheDeleteHooks = [];
const wrappedFetchFn = wrapFetchWithHooks(
onFetchHooks,
() => instrumentation,
logger
);
const wrappedCache = config.cache ? wrapCacheWithHooks({
cache: config.cache,
onCacheGet: onCacheGetHooks,
onCacheSet: onCacheSetHooks,
onCacheDelete: onCacheDeleteHooks
}) : void 0;
const pubsub = config.pubsub;
const configContext = {
fetch: wrappedFetchFn,
logger,
cwd: config.cwd || (typeof process !== "undefined" ? process.cwd() : ""),
cache: wrappedCache,
pubsub
};
let unifiedGraphPlugin;
const readinessCheckEndpoint = config.readinessCheckEndpoint || "/readiness";
const onSubgraphExecuteHooks = [];
const onDelegateHooks = [];
const onDelegationPlanHooks = [];
const onDelegationStageExecuteHooks = [];
let unifiedGraph;
let schemaInvalidator;
let getSchema = () => unifiedGraph;
let contextBuilder;
let readinessChecker;
let getExecutor;
let replaceSchema = (newSchema) => {
unifiedGraph = newSchema;
};
const { name: reportingTarget, plugin: registryPlugin } = getReportingPlugin(
config,
configContext
);
let persistedDocumentsPlugin = {};
if (config.reporting?.type !== "hive" && config.persistedDocuments && "type" in config.persistedDocuments && config.persistedDocuments?.type === "hive") {
persistedDocumentsPlugin = useHiveConsole({
...configContext,
enabled: false,
// disables only usage reporting
logger: configContext.logger.child({
plugin: "Hive Persisted Documents"
}),
experimental__persistedDocuments: {
cdn: {
endpoint: config.persistedDocuments.endpoint,
accessToken: config.persistedDocuments.token
},
allowArbitraryDocuments: !!config.persistedDocuments.allowArbitraryDocuments
}
});
} else if (config.persistedDocuments && "getPersistedOperation" in config.persistedDocuments) {
persistedDocumentsPlugin = usePersistedOperations({
...configContext,
...config.persistedDocuments
});
}
let subgraphInformationHTMLRenderer = () => "";
if ("proxy" in config) {
let continuePolling2 = function() {
if (currentTimeout) {
clearTimeout(currentTimeout);
}
if (pollingInterval) {
currentTimeout = setTimeout(schemaFetcher, pollingInterval);
}
}, pausePolling2 = function() {
if (currentTimeout) {
clearTimeout(currentTimeout);
}
};
const transportExecutorStack = new AsyncDisposableStack();
const proxyExecutor = getProxyExecutor({
config,
configContext,
getSchema() {
return unifiedGraph;
},
onSubgraphExecuteHooks,
transportExecutorStack,
instrumentation: () => instrumentation
});
getExecutor = () => proxyExecutor;
let currentTimeout;
const pollingInterval = config.pollingInterval;
let initialFetch$;
let schemaFetcher;
if (config.schema && typeof config.schema === "object" && "type" in config.schema) {
const { endpoint, key } = config.schema;
const fetcher = createSchemaFetcher({
endpoint,
key,
logger: configContext.logger.child({ source: "Hive CDN" })
});
schemaFetcher = function fetchSchemaFromCDN() {
pausePolling2();
initialFetch$ = handleMaybePromise(fetcher, ({ sdl }) => {
{
unifiedGraph = buildSchema(sdl, {
assumeValid: true,
assumeValidSDL: true
});
}
continuePolling2();
return true;
});
return initialFetch$;
};
} else if (config.schema) {
if (!isDynamicUnifiedGraphSchema(config.schema)) {
delete config.pollingInterval;
}
schemaFetcher = function fetchSchema() {
pausePolling2();
initialFetch$ = handleMaybePromise(
() => handleUnifiedGraphConfig(
// @ts-expect-error TODO: what's up with type narrowing
config.schema,
configContext
),
(schema) => {
if (isSchema(schema)) {
unifiedGraph = schema;
} else if (isDocumentNode(schema)) {
unifiedGraph = buildASTSchema(schema, {
assumeValid: true,
assumeValidSDL: true
});
} else {
unifiedGraph = buildSchema(schema, {
noLocation: true,
assumeValid: true,
assumeValidSDL: true
});
}
continuePolling2();
return true;
}
);
return initialFetch$;
};
} else {
schemaFetcher = function fetchSchemaWithExecutor() {
pausePolling2();
return handleMaybePromise(
() => schemaFromExecutor(proxyExecutor, configContext, {
assumeValid: true
}),
(schema) => {
unifiedGraph = schema;
continuePolling2();
return true;
},
(err) => {
configContext.logger.warn(`Failed to introspect schema`, err);
return true;
}
);
};
}
getSchema = () => {
if (unifiedGraph != null) {
return unifiedGraph;
}
if (initialFetch$ != null) {
return handleMaybePromise(
() => initialFetch$,
() => unifiedGraph
);
}
return handleMaybePromise(schemaFetcher, () => unifiedGraph);
};
const shouldSkipValidation = "skipValidation" in config ? config.skipValidation : false;
const executorPlugin = {
onValidate({ params, setResult }) {
if (shouldSkipValidation || !params.schema) {
setResult([]);
}
},
onDispose() {
pausePolling2();
return transportExecutorStack.disposeAsync();
}
};
unifiedGraphPlugin = executorPlugin;
readinessChecker = () => handleMaybePromise(
() => proxyExecutor({
document: parse(`query ReadinessCheck { __typename }`)
}),
(res) => !isAsyncIterable$1(res) && !!res.data?.__typename
);
schemaInvalidator = () => {
unifiedGraph = void 0;
initialFetch$ = schemaFetcher();
};
subgraphInformationHTMLRenderer = () => {
const endpoint = config.proxy.endpoint;
const htmlParts = [];
htmlParts.push(`<h2>Proxy Mode</h2>`);
if (config.schema) {
if (typeof config.schema === "object" && "type" in config.schema) {
htmlParts.push(
`<p>From ${config.schema.type === "hive" ? "Hive" : "Unknown"} CDN</p>`
);
} else if (isValidPath(config.schema) || isUrl(String(config.schema))) {
if (isUrl(String(config.schema))) {
htmlParts.push(
`<p>From <a href="${config.schema}">${config.schema}</p>`
);
} else {
htmlParts.push(`<p>From <code>${config.schema}</code></p>`);
}
} else {
htmlParts.push(`<p>Using GraphQL Schema in Config</p>`);
}
}
htmlParts.push("<br>");
htmlParts.push(
`<div class="var">
<label for="endpoint">Endpoint</label>
<code id="endpoint">${endpoint}</code>
</div>`
);
if (reportingTarget) {
htmlParts.push(
`<br><p>Usage Reporting Sent to <u>${reportingTarget}</u></p>`
);
}
return htmlParts.join("");
};
} else if ("subgraph" in config) {
let getSubschemaConfig2 = function() {
if (getSubschemaConfig$ == null) {
getSubschemaConfig$ = handleMaybePromise(
() => handleUnifiedGraphConfig(subgraphInConfig, configContext),
(newUnifiedGraph) => {
if (isSchema(newUnifiedGraph)) {
unifiedGraph = newUnifiedGraph;
} else if (isDocumentNode(newUnifiedGraph)) {
unifiedGraph = buildASTSchema(newUnifiedGraph, {
assumeValid: true,
assumeValidSDL: true
});
} else {
unifiedGraph = buildSchema(newUnifiedGraph, {
noLocation: true,
assumeValid: true,
assumeValidSDL: true
});
}
unifiedGraph = restoreExtraDirectives(unifiedGraph);
subschemaConfig = {
name: getDirectiveExtensions(unifiedGraph)?.["transport"]?.[0]?.["subgraph"],
schema: unifiedGraph
};
const transportEntryMap = getTransportEntryMapUsingFusionAndFederationDirectives(
unifiedGraph,
config.transportEntries
);
const additionalTypeDefs = [];
const stitchingDirectivesTransformer = getStitchingDirectivesTransformerForSubschema();
const onSubgraphExecute = getOnSubgraphExecute({
onSubgraphExecuteHooks,
...config.transports ? { transports: config.transports } : {},
transportContext: configContext,
transportEntryMap,
getSubgraphSchema() {
return unifiedGraph;
},
transportExecutorStack,
instrumentation: () => instrumentation
});
subschemaConfig = handleFederationSubschema({
subschemaConfig,
additionalTypeDefs,
stitchingDirectivesTransformer,
onSubgraphExecute
});
unifiedGraph = wrapSchema(subschemaConfig);
const entities = Object.keys(subschemaConfig.merge || {});
let entitiesDef = "union _Entity";
if (entities.length) {
entitiesDef += ` = ${entities.join(" | ")}`;
}
const additionalResolvers = asArray(
"additionalResolvers" in config ? config.additionalResolvers : []
).filter((r) => r != null);
const queryTypeName = unifiedGraph.getQueryType()?.name || "Query";
const finalTypeDefs = handleResolveToDirectives(
parse(
/* GraphQL */
`
type ${queryTypeName} {
${entities.length ? "_entities(representations: [_Any!]!): [_Entity]!" : ""}
_service: _Service!
}
scalar _Any
${entities.length ? entitiesDef : ""}
type _Service {
sdl: String
}
`
),
additionalTypeDefs,
additionalResolvers
);
additionalResolvers.push({
[queryTypeName]: {
_service() {
return {
sdl() {
if (isSchema(newUnifiedGraph)) {
return printSchemaWithDirectives(newUnifiedGraph);
}
if (isDocumentNode(newUnifiedGraph)) {
return defaultPrintFn$1(newUnifiedGraph);
}
return newUnifiedGraph;
}
};
}
}
});
if (entities.length) {
additionalResolvers.push({
[queryTypeName]: {
_entities(_root, args, context, info) {
if (Array.isArray(args.representations)) {
return args.representations.map((representation) => {
const typeName = representation.__typename;
const mergeConfig = subschemaConfig.merge?.[typeName];
const entryPoints = mergeConfig?.entryPoints || [
mergeConfig
];
const satisfiedEntryPoint = entryPoints.find(
(entryPoint) => {
if (entryPoint?.selectionSet) {
const selectionSet = parseSelectionSet(
entryPoint.selectionSet,
{
noLocation: true
}
);
return checkIfDataSatisfiesSelectionSet(
selectionSet,
representation
);
}
return true;
}
);
if (satisfiedEntryPoint) {
if (satisfiedEntryPoint.key) {
return handleMaybePromise(
() => batchDelegateToSchema({
schema: subschemaConfig,
...satisfiedEntryPoint.fieldName ? {
fieldName: satisfiedEntryPoint.fieldName
} : {},
key: satisfiedEntryPoint.key(representation),
...satisfiedEntryPoint.argsFromKeys ? {
argsFromKeys: satisfiedEntryPoint.argsFromKeys
} : {},
...satisfiedEntryPoint.valuesFromResults ? {
valuesFromResults: satisfiedEntryPoint.valuesFromResults
} : {},
context,
info
}),
(res) => mergeDeep([representation, res])
);
}
if (satisfiedEntryPoint.args) {
return handleMaybePromise(
() => delegateToSchema({
schema: subschemaConfig,
...satisfiedEntryPoint.fieldName ? {
fieldName: satisfiedEntryPoint.fieldName
} : {},
args: satisfiedEntryPoint.args(
representation
),
context,
info
}),
(res) => mergeDeep([representation, res])
);
}
}
return representation;
});
}
return [];
}
}
});
}
unifiedGraph = mergeSchemas({
assumeValid: true,
assumeValidSDL: true,
schemas: [unifiedGraph],
typeDefs: finalTypeDefs,
resolvers: additionalResolvers
});
contextBuilder = (base) => (
// @ts-expect-error - Typings are wrong in legacy Mesh
Object.assign(
// @ts-expect-error - Typings are wrong in legacy Mesh
base,
getInContextSDK(
unifiedGraph,
// @ts-expect-error - Typings are wrong in legacy Mesh
[subschemaConfig],
configContext.logger,
onDelegateHooks
)
)
);
return true;
}
);
}
return getSubschemaConfig$;
};
const subgraphInConfig = config.subgraph;
let getSubschemaConfig$;
let subschemaConfig;
const transportExecutorStack = new AsyncDisposableStack();
getSchema = () => handleMaybePromise(getSubschemaConfig2, () => unifiedGraph);
schemaInvalidator = () => {
getSubschemaConfig$ = void 0;
};
unifiedGraphPlugin = {
onDispose() {
return transportExecutorStack.disposeAsync();
}
};
} else {
let unifiedGraphFetcher;
let supergraphLoadedPlace;
if (typeof config.supergraph === "object" && "type" in config.supergraph) {
if (config.supergraph.type === "hive") {
const { endpoint, key } = config.supergraph;
supergraphLoadedPlace = "Hive CDN <br>" + endpoint;
const fetcher = createSupergraphSDLFetcher({
endpoint,
key,
logger: configContext.logger.child({ source: "Hive CDN" }),
// @ts-expect-error - MeshFetch is not compatible with `typeof fetch`
fetchImplementation: configContext.fetch
});
unifiedGraphFetcher = () => fetcher().then(({ supergraphSdl }) => supergraphSdl);
} else if (config.supergraph.type === "graphos") {
const graphosFetcherContainer = createGraphOSFetcher({
graphosOpts: config.supergraph,
configContext,
pollingInterval: config.pollingInterval
});
unifiedGraphFetcher = graphosFetcherContainer.unifiedGraphFetcher;
supergraphLoadedPlace = graphosFetcherContainer.supergraphLoadedPlace;
} else {
unifiedGraphFetcher = () => {
throw new Error(
`Unknown supergraph configuration: ${config.supergraph}`
);
};
}
} else {
if (!isDynamicUnifiedGraphSchema(config.supergraph)) {
logger.debug(`Disabling polling for static supergraph`);
delete config.pollingInterval;
} else if (!config.pollingInterval) {
logger.debug(
`Polling interval not set for supergraph, if you want to get updates of supergraph, we recommend setting a polling interval`
);
}
unifiedGraphFetcher = () => handleUnifiedGraphConfig(
// @ts-expect-error TODO: what's up with type narrowing
config.supergraph,
configContext
);
if (typeof config.supergraph === "function") {
const fnName = config.supergraph.name || "";
supergraphLoadedPlace = `a custom loader ${fnName}`;
} else if (typeof config.supergraph === "string") {
supergraphLoadedPlace = config.supergraph;
}
}
const unifiedGraphManager = new UnifiedGraphManager({
getUnifiedGraph: unifiedGraphFetcher,
onUnifiedGraphChange(newUnifiedGraph) {
unifiedGraph = newUnifiedGraph;
replaceSchema(newUnifiedGraph);
},
transports: config.transports,
transportEntryAdditions: config.transportEntries,
pollingInterval: config.pollingInterval,
transportContext: configContext,
onDelegateHooks,
onSubgraphExecuteHooks,
onDelegationPlanHooks,
onDelegationStageExecuteHooks,
additionalTypeDefs: config.additionalTypeDefs,
additionalResolvers: config.additionalResolvers,
instrumentation: () => instrumentation,
batch: config.__experimental__batchDelegation
});
getSchema = () => unifiedGraphManager.getUnifiedGraph();
readinessChecker = () => {
const logger2 = configContext.logger.child("readiness");
logger2.debug(`checking`);
return handleMaybePromise(
() => unifiedGraphManager.getUnifiedGraph(),
(schema) => {
if (!schema) {
logger2.debug(
`failed because supergraph has not been loaded yet or failed to load`
);
return false;
}
logger2.debug("passed");
return true;
},
(err) => {
logger2.error(
`failed due to errors on loading supergraph:
${err.stack || err.message}`
);
return false;
}
);
};
schemaInvalidator = () => unifiedGraphManager.invalidateUnifiedGraph();
contextBuilder = (base) => unifiedGraphManager.getContext(base);
getExecutor = () => unifiedGraphManager.getExecutor();
unifiedGraphPlugin = {
onDispose() {
return dispose(unifiedGraphManager);
}
};
subgraphInformationHTMLRenderer = () => handleMaybePromise(
() => handleMaybePromise(
() => unifiedGraphManager.getTransportEntryMap(),
(transportEntryMap) => ({
transportEntryMap,
loadError: void 0,
loaded: true
}),
(loadError) => ({
transportEntryMap: {},
loadError,
loaded: false
})
),
({ transportEntryMap, loaded, loadError }) => {
const htmlParts = [];
htmlParts.push(`<h2>Supergraph Status</h2>`);
const sourceHtmlPart = supergraphLoadedPlace ? `<div class="var">
<label for="source">Source</label>
<code id="source">${supergraphLoadedPlace}</code>
</div>` : "";
if (loaded) {
htmlParts.push(`<p>\u2705 Loaded</p><br>`);
htmlParts.push(sourceHtmlPart);
if (reportingTarget) {
htmlParts.push(
`<br><p>Usage Reporting Sent to <u>${reportingTarget}</u></p>`
);
}
htmlParts.push(`<br>`);
htmlParts.push(`<table>`);
htmlParts.push(
`<tr><th>Subgraph</th><th>Transport</th><th>Location</th></tr>`
);
for (const subgraphName in transportEntryMap) {
const transportEntry = transportEntryMap[subgraphName];
htmlParts.push(`<tr>`);
htmlParts.push(`<td>${subgraphName}</td>`);
htmlParts.push(`<td>${transportEntry.kind}</td>`);
if (transportEntry.location && isUrl(transportEntry.location)) {
htmlParts.push(
`<td><a href="${transportEntry.location}">${transportEntry.location}</a></td>`
);
} else {
htmlParts.push(
`<td><code>${transportEntry.location}</code></td>`
);
}
htmlParts.push(`</tr>`);
}
htmlParts.push(`</table>`);
} else if (loadError) {
htmlParts.push(`<p>\u274C Failed</p><br>`);
htmlParts.push(sourceHtmlPart);
htmlParts.push(`<br>`);
htmlParts.push(
`<pre><code>${loadError instanceof Error ? loadError.stack : JSON.stringify(loadError, null, " ")}</code></pre>`
);
} else {
htmlParts.push(`<p>\u26A0\uFE0F Unknown</p><br>`);
htmlParts.push(sourceHtmlPart);
}
return htmlParts.join("");
}
);
}
const readinessCheckPlugin = useReadinessCheck({
endpoint: readinessCheckEndpoint,
// @ts-expect-error PromiseLike is not compatible with Promise
check: readinessChecker
});
const defaultGatewayPlugin = {
onFetch({ setFetchFn }) {
if (fetchAPI?.fetch) {
setFetchFn(fetchAPI.fetch);
}
},
onRequestParse() {
return handleMaybePromise(getSchema, (schema) => {
replaceSchema(schema);
});
},
onPluginInit({ plugins, setSchema }) {
replaceSchema = setSchema;
onFetchHooks.splice(0, onFetchHooks.length);
onSubgraphExecuteHooks.splice(0, onSubgraphExecuteHooks.length);
onDelegateHooks.splice(0, onDelegateHooks.length);
for (const plugin of plugins) {
if (plugin.instrumentation) {
instrumentation = instrumentation ? chain(instrumentation, plugin.instrumentation) : plugin.instrumentation;
}
if (plugin.onFetch) {
onFetchHooks.push(plugin.onFetch);
}
if (plugin.onSubgraphExecute) {
onSubgraphExecuteHooks.push(plugin.onSubgraphExecute);
}
if (plugin.onDelegate) {
onDelegateHooks.push(plugin.onDelegate);
}
if (plugin.onDelegationPlan) {
onDelegationPlanHooks.push(plugin.onDelegationPlan);
}
if (plugin.onDelegationStageExecute) {
onDelegationStageExecuteHooks.push(plugin.onDelegationStageExecute);
}
if (plugin.onCacheGet) {
onCacheGetHooks.push(plugin.onCacheGet);
}
if (plugin.onCacheSet) {
onCacheSetHooks.push(plugin.onCacheSet);
}
if (plugin.onCacheDelete) {
onCacheDeleteHooks.push(plugin.onCacheDelete);
}
}
}
};
if (getExecutor) {
const onExecute = ({
setExecuteFn
}) => handleMaybePromise(
() => getExecutor?.(),
(executor) => {
if (executor) {
const executeFn = getExecuteFnFromExecutor(executor);
setExecuteFn(executeFn);
}
}
);
const onSubscribe = ({
setSubscribeFn
}) => handleMaybePromise(
() => getExecutor?.(),
(executor) => {
if (executor) {
const subscribeFn = getExecuteFnFromExecutor(executor);
setSubscribeFn(subscribeFn);
}
}
);
defaultGatewayPlugin.onExecute = onExecute;
defaultGatewayPlugin.onSubscribe = onSubscribe;
}
const productName = config.productName || "Hive Gateway";
const productDescription = config.productDescription || "Federated GraphQL Gateway";
const productPackageName = config.productPackageName || "@graphql-hive/gateway";
const productLogo = config.productLogo || defaultProductLogo;
const productLink = config.productLink || "https://the-guild.dev/graphql/hive/docs/gateway";
let graphiqlOptionsOrFactory;
if (config.graphiql == null || config.graphiql === true) {
graphiqlOptionsOrFactory = {
title: productName,
defaultQuery: defaultQueryText
};
} else if (config.graphiql === false) {
graphiqlOptionsOrFactory = false;
} else if (typeof config.graphiql === "object") {
graphiqlOptionsOrFactory = {
title: productName,
defaultQuery: defaultQueryText,
...config.graphiql
};
} else if (typeof config.graphiql === "function") {
const userGraphiqlFactory = config.graphiql;
graphiqlOptionsOrFactory = function graphiqlOptionsFactoryForMesh(...args) {
return handleMaybePromise(
() => userGraphiqlFactory(...args),
(resolvedOpts) => {
if (resolvedOpts === false) {
return false;
}
if (resolvedOpts === true) {
return {
title: productName,
defaultQuery: defaultQueryText
};
}
return {
title: productName,
defaultQuery: defaultQueryText,
...resolvedOpts
};
}
);
};
}
let landingPageRenderer;
if (config.landingPage == null || config.landingPage === true) {
landingPageRenderer = (opts) => handleMaybePromise(
subgraphInformationHTMLRenderer,
(subgraphHtml) => new opts.fetchAPI.Response(
landingPageHtml.replace(/__GRAPHIQL_LINK__/g, opts.graphqlEndpoint).replace(/__REQUEST_PATH__/g, opts.url.pathname).replace(/__SUBGRAPH_HTML__/g, subgraphHtml).replaceAll(/__PRODUCT_NAME__/g, productName).replaceAll(/__PRODUCT_DESCRIPTION__/g, productDescription).replaceAll(/__PRODUCT_PACKAGE_NAME__/g, productPackageName).replace(/__PRODUCT_LINK__/, productLink).replace(/__PRODUCT_LOGO__/g, productLogo),
{
status: 200,
statusText: "OK",
headers: {
"Content-Type": "text/html"
}
}
)
);
} else if (typeof config.landingPage === "function") {
landingPageRenderer = config.landingPage;
} else if (config.landingPage === false) {
landingPageRenderer = false;
}
const basePlugins = [
defaultGatewayPlugin,
unifiedGraphPlugin,
readinessCheckPlugin,
registryPlugin,
persistedDocumentsPlugin,
useRetryOnSchemaReload({ logger })
];
if (config.subgraphErrors !== false) {
basePlugins.push(
useSubgraphErrorPlugin(
typeof config.subgraphErrors === "object" ? config.subgraphErrors : void 0
)
);
}
if (config.requestId !== false) {
const reqIdPlugin = useRequestId(
typeof config.requestId === "object" ? config.requestId : void 0
);
basePlugins.push(reqIdPlugin);
}
if (isDisposable(wrappedCache)) {
const cacheDisposePlugin = {
onDispose() {
return dispose(wrappedCache);
}
};
basePlugins.push(cacheDisposePlugin);
}
if (isDisposable(pubsub)) {
const cacheDisposePlugin = {
onDispose() {
return dispose(pubsub);
}
};
basePlugins.push(cacheDisposePlugin);
}
const extraPlugins = [];
if (config.webhooks) {
extraPlugins.push(useWebhooks(configContext));
}
if (config.responseCaching) {
extraPlugins.push(
// @ts-expect-error TODO: what's up with type narrowing
useMeshResponseCache({
...configContext,
...config.responseCaching
})
);
}
if (config.contentEncoding) {
extraPlugins.push(
useContentEncoding(
typeof config.contentEncoding === "object" ? config.contentEncoding : {}
)
);
}
if (config.deferStream) {
extraPlugins.push(useDeferStream());
}
if (config.executionCancellation) {
extraPlugins.push(useExecutionCancellation());
}
if (config.upstreamCancellation) {
extraPlugins.push(useUpstreamCancel());
}
if (config.disableIntrospection) {
extraPlugins.push(
useDisableIntrospection(
// @ts-expect-error - Should be fixed in the envelop plugin
typeof config.disableIntrospection === "object" ? config.disableIntrospection : {}
)
);
}
if (config.csrfPrevention) {
extraPlugins.push(
useCSRFPrevention(
typeof config.csrfPrevention === "object" ? config.csrfPrevention : {}
)
);
}
if (config.customAgent) {
extraPlugins.push(useCustomAgent(config.customAgent));
}
if (config.genericAuth) {
extraPlugins.push(useGenericAuth(config.genericAuth));
}
if (config.hmacSignature) {
extraPlugins.push(useHmacUpstreamSignature(config.hmacSignature));
}
if (config.propagateHeaders) {
extraPlugins.push(usePropagateHeaders(config.propagateHeaders));
}
if (config.upstreamTimeout) {
extraPlugins.push(useUpstreamTimeout(config.upstreamTimeout));
}
if (config.upstreamRetry) {
extraPlugins.push(useUpstreamRetry(config.upstreamRetry));
}
if (config.demandControl) {
extraPlugins.push(useDemandControl(config.demandControl));
}
if (config.cookies) {
extraPlugins.push(useCookies());
}
let isDebug = false;
if ("level" in logger) {
if (logger.level === "debug" || logger.level === LogLevel.debug) {
isDebug = true;
}
} else {
logger.debug(() => {
isDebug = true;
return "Debug mode enabled";
});
}
if (isDebug) {
extraPlugins.push(
useSubgraphExecuteDebug(configContext),
useFetchDebug(configContext),
useDelegationPlanDebug(configContext),
useCacheDebug(configContext)
);
}
const yoga = createYoga({
// @ts-expect-error Types???
schema: unifiedGraph,
// @ts-expect-error MeshFetch is not compatible with YogaFetch
fetchAPI: config.fetchAPI,
logging: logger,
plugins: [
...basePlugins,
...extraPlugins,
...config.plugins?.(configContext) || []
],
context({ request, params, req, connectionParams }) {
let headers = (
// Maybe Node-like environment
req?.headers ? getHeadersObj(req.headers) : (
// Fetch environment
request?.headers ? getHeadersObj(request.headers) : (
// Unknown environment
{}
)
)
);
if (connectionParams) {
headers = { ...headers, ...connectionParams };
}
const baseContext = {
...configContext,
request,
params,
headers,
connectionParams: headers
};
if (contextBuilder) {
return contextBuilder(baseContext);
}
return baseContext;
},
cors: config.cors,
graphiql: graphiqlOptionsOrFactory,
renderGraphiQL: config.renderGraphiQL,
batching: config.batching,
graphqlEndpoint: config.graphqlEndpoint,
maskedErrors: config.maskedErrors,
healthCheckEndpoint: config.healthCheckEndpoint || "/healthcheck",
landingPage: landingPageRenderer,
disposeOnProcessTerminate: true
});
fetchAPI ||= yoga.fetchAPI;
Object.defineProperties(yoga, {
version: {
get() {
return globalThis.__VERSION__;
}
},
invalidateUnifiedGraph: {
value: schemaInvalidator,
configurable: true
},
getSchema: {
value: getSchema,
configurable: true
}
});
return yoga;
}
function isDynamicUnifiedGraphSchema(schema) {
if (isSchema(schema)) {
return false;
}
if (isDocumentNode(schema)) {
return false;
}
if (typeof schema === "string") {
if (isUrl(schema)) {
return true;
}
if (isValidPath(schema)) {
return false;
}
try {
parse(schema);
return false;
} catch (e) {
}
}
return true;
}
function useCustomFetch(fetch) {
return {
onFetch({ setFetchFn }) {
setFetchFn(fetch);
}
};
}
const useStaticFiles = ({
baseDir,
staticFiles = "public"
}) => {
return {
onRequest({ request, url, endResponse, fetchAPI }) {
if (request.method === "GET") {
let relativePath = url.pathname;
if (relativePath === "/" || !relativePath) {
relativePath = "index.html";
}
const absoluteStaticFilesPath = path.join(baseDir, staticFiles);
const absolutePath = path.join(absoluteStaticFilesPath, relativePath);
if (absolutePath.startsWith(absoluteStaticFilesPath)) {
return pathExists(absolutePath).then((exists) => {
if (exists) {
const readStream = fs.createReadStream(absolutePath);
endResponse(
new fetchAPI.Response(readStream, {
status: 200
})
);
}
});
}
}
}
};
};
function getGraphQLWSOptions(gwRuntime, onContext) {
return {
execute: (args) => args.rootValue.execute(args),
subscribe: (args) => args.rootValue.subscribe(args),
onSubscribe: async (ctx, idOrMessage, payloadOrUndefined) => {
let payload;
if (typeof idOrMessage === "string") {
payload = payloadOrUndefined;
} else {
payload = idOrMessage.payload;
}
const { schema, execute: execute2, subscribe: subscribe2, contextFactory, parse, validate } = gwRuntime.getEnveloped({
connectionParams: ctx.connectionParams,
waitUntil: gwRuntime.waitUntil,
...await onContext(ctx)
});
const args = {
schema: schema || await gwRuntime.getSchema(),
operationName: payload.operationName,
document: parse(payload.query),
variableValues: payload.variables,
contextValue: await contextFactory(),
rootValue: {
execute: execute2,
subscribe: subscribe2
}
};
if (args.schema) {
const errors = validate(args.schema, args.document);
if (errors.length) return errors;
}
return args;
}
};
}
export { createGatewayRuntime, getDefaultLogger, getGraphQLWSOptions, getProxyExecutor, handleLoggingConfig, useCustomFetch, usePropagateHeaders, useStaticFiles, useUpstreamRetry, useUpstreamTimeout };