{"version":3,"file":"execution.cjs","sources":["../../../../../packages/engine-http/src/graphql/execution.ts"],"sourcesContent":["import {\n\tDocumentNode,\n\texecute,\n\tGraphQLError,\n\tGraphQLSchema,\n\tKind,\n\tOperationDefinitionNode,\n\tOperationTypeNode,\n\tparse,\n\tvalidate,\n\tvalidateSchema,\n} from 'graphql'\nimport { LRUCache } from 'lru-cache'\nimport { createHash } from 'node:crypto'\nimport { Request, Response } from 'koa'\nimport { logger } from '@contember/logger'\nimport { ForbiddenError } from '@contember/graphql-utils'\nimport { UserError } from '@contember/engine-content-api'\n\nexport interface GraphQLListener<Context> {\n\tonStart?: (ctx: {}) => Omit<GraphQLListener<Context>, 'onStart'> | void\n\tonExecute?: (ctx: {\n\t\tcontext: Context\n\t\tdocument: DocumentNode\n\t\toperation: OperationTypeNode\n\t}) => Omit<GraphQLListener<Context>, 'onStart' | 'onExecute'> | void\n\tonResponse?: (ctx: {\n\t\tresponse: any\n\t\tcontext: Context\n\t}) => Omit<GraphQLListener<Context>, 'onStart' | 'onExecute' | 'onResponse'> | void\n\tonShutdown?: (ctx: {\n\t\tresponse: any\n\t}) => Omit<GraphQLListener<Context>, 'onStart' | 'onExecute' | 'onResponse' | 'onShutdown'> | void\n}\n\ninterface FactoryArgs<Context> {\n\tschema: GraphQLSchema\n\tlisteners: GraphQLListener<Context>[]\n}\n\nexport type GraphQLQueryHandler<Context> = (args: { request: Request; response: Response; createContext: ({}: { operation: OperationTypeNode }) => Context }) => any\n\nconst hitCacheMaxAgeSeconds = 10 * 60\nconst documentCacheMaxAgeSeconds = hitCacheMaxAgeSeconds * 10\nconst pruneIntervalSeconds = documentCacheMaxAgeSeconds / 2\nconst documentCacheMax = 100\nconst hitCacheMax = documentCacheMax * 2\n\nexport const createGraphQLQueryHandler = <Context>({\n\tschema,\n\tlisteners,\n}: FactoryArgs<Context>): GraphQLQueryHandler<Context> => {\n\tlet schemaValidated = false\n\tconst hitCache = new LRUCache<string, true>({\n\t\tmax: hitCacheMax,\n\t\tttl: hitCacheMaxAgeSeconds * 1000,\n\t})\n\tconst documentCache = new LRUCache<string, DocumentNode>({\n\t\tmax: documentCacheMax,\n\t\tttl: documentCacheMaxAgeSeconds * 1000,\n\t})\n\tlet lastPrune = Date.now()\n\n\treturn async ({ request, response, createContext }) => {\n\n\t\tconst now = Date.now()\n\t\tif ((now - lastPrune) > pruneIntervalSeconds * 1000) {\n\t\t\tdocumentCache.purgeStale()\n\t\t\tlastPrune = now\n\t\t}\n\t\tconst listenersQueue = [...listeners]\n\n\t\tlistenersQueue.forEach(it => {\n\t\t\tit.onStart && listenersQueue.push(it.onStart({}) || {})\n\t\t})\n\n\t\tconst respond = (code: number, data: any) => {\n\t\t\tresponse.status = code\n\t\t\tresponse.body = JSON.stringify(data)\n\t\t\tresponse.set('Content-type', 'application/json')\n\n\t\t\tlistenersQueue.forEach(it => {\n\t\t\t\tit.onShutdown && listenersQueue.push(it.onShutdown({ response: data }) || {})\n\t\t\t})\n\t\t}\n\t\ttry {\n\t\t\tif (!schemaValidated) {\n\t\t\t\tconst validationResult = validateSchema(schema)\n\t\t\t\tif (validationResult.length) {\n\t\t\t\t\treturn respond(400, {\n\t\t\t\t\t\terrors: validationResult,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tschemaValidated = true\n\t\t\t}\n\t\t\tconst resolvedRequest = request.method === 'POST' ? (request.body as any) : request.query\n\t\t\tif (!resolvedRequest.query) {\n\t\t\t\treturn respond(400, {\n\t\t\t\t\terrors: [{ message: 'Missing request query' }],\n\t\t\t\t})\n\t\t\t}\n\t\t\tconst queryHash = createHash('md5').update(resolvedRequest.query).digest('hex')\n\t\t\tlet doc = documentCache.get(queryHash)\n\t\t\tif (!doc) {\n\t\t\t\tdoc = parse(resolvedRequest.query)\n\t\t\t\tconst validationResult = validate(schema, doc)\n\t\t\t\tif (validationResult.length) {\n\t\t\t\t\treturn respond(400, { errors: validationResult })\n\t\t\t\t}\n\t\t\t\tif (hitCache.get(queryHash)) {\n\t\t\t\t\tdocumentCache.set(queryHash, doc)\n\t\t\t\t}\n\t\t\t\thitCache.set(queryHash, true)\n\t\t\t}\n\t\t\tconst document = doc\n\t\t\tconst operationName = resolvedRequest.operationName ?? null\n\t\t\tconst operation = resolveOperationType(document, operationName)\n\n\n\t\t\tconst context = createContext({ operation })\n\t\t\tlistenersQueue.forEach(it => {\n\t\t\t\tit.onExecute && listenersQueue.push(it.onExecute({ context, document, operation }) || {})\n\t\t\t})\n\t\t\tconst response = await execute({\n\t\t\t\tschema,\n\t\t\t\tdocument,\n\t\t\t\toperationName: operationName,\n\t\t\t\tvariableValues: resolvedRequest.variables,\n\t\t\t\tcontextValue: context,\n\t\t\t})\n\t\t\tlistenersQueue.forEach(it => {\n\t\t\t\tit.onResponse && listenersQueue.push(it.onResponse({ context, response }) || {})\n\t\t\t})\n\t\t\tif (response.errors && response.errors.length > 0) {\n\t\t\t\tconst [code, errors] = processErrors(response.errors)\n\t\t\t\trespond(code && !response.data ? code : 200, { ...response, errors })\n\t\t\t} else {\n\t\t\t\trespond(200, response)\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tif (e instanceof GraphQLError) {\n\t\t\t\treturn respond(e instanceof ForbiddenError ? 403 : 400, { errors: [e] })\n\t\t\t}\n\t\t\tlogger.error(e)\n\t\t\treturn respond(500, { errors: [{ message: 'Internal error' }] })\n\t\t}\n\t}\n}\n\n\nexport const extractOriginalError = (e: Error): Error => {\n\tif (e instanceof GraphQLError && e.originalError) {\n\t\treturn extractOriginalError(e.originalError)\n\t}\n\tif ('errors' in e && Array.isArray((e as any).errors) && (e as any).errors.length === 1) {\n\t\treturn extractOriginalError((e as any).errors[0])\n\t}\n\treturn e\n}\n\nconst processErrors = (errors: readonly any[]): [number | null, any[]] => {\n\tconst resultErrors = []\n\tlet has400 = false\n\tlet has403 = false\n\tlet has500 = false\n\tfor (const error of errors) {\n\t\tconst originalError = extractOriginalError(error)\n\t\tif (originalError instanceof GraphQLError) {\n\t\t\tresultErrors.push(error)\n\t\t\thas400 = true\n\t\t} else if (originalError instanceof ForbiddenError) {\n\t\t\tresultErrors.push(error)\n\t\t\thas403 = true\n\t\t} else if (originalError instanceof UserError) {\n\t\t\tresultErrors.push({ message: error.message, locations: error.locations, path: error.path })\n\t\t\thas400 = true\n\t\t} else {\n\t\t\tlogger.error(originalError || error)\n\t\t\tresultErrors.push({ message: 'Internal server error', locations: undefined, path: undefined })\n\t\t\thas500 = true\n\t\t}\n\t}\n\treturn [has500 ? 500 : has400 ? 400 : has403 ? 403 : null, resultErrors]\n}\n\n\nconst resolveOperationType = (document: DocumentNode, operationName: string | null): OperationTypeNode => {\n\tfor (const definition of document.definitions) {\n\t\tif (definition.kind === Kind.OPERATION_DEFINITION) {\n\t\t\tif (operationName === null || definition.name?.value === operationName) {\n\t\t\t\treturn definition.operation\n\t\t\t}\n\t\t}\n\t}\n\tthrow new GraphQLError('Must provide an operation.')\n}\n"],"names":["LRUCache","validateSchema","createHash","parse","validate","response","execute","GraphQLError","ForbiddenError","logger","UserError","Kind"],"mappings":"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"}