From 9f9f6fe61c74eaa6572866eddd97c348307107a8 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Wed, 27 Nov 2024 05:56:34 -0500 Subject: [PATCH] Fix Hive Transform and additional resolvers import issues (#8007) * fix(hive-transform): usage reporting * No autodISPOSE * Fix Information * Improvements on importing * Fix arguments node * lets go * chore(dependencies): updated changesets for modified dependencies * Go * Go * Fix artifacts * Fix integrity * Fix * Fix TS issue * Use the given schema --------- Co-authored-by: github-actions[bot] --- ...@graphql-mesh_include-8007-dependencies.md | 5 ++ .changeset/cuddly-spies-argue.md | 5 ++ .changeset/fresh-insects-give.md | 8 +++ .changeset/silver-days-sort.md | 6 +++ .changeset/thick-goats-type.md | 5 ++ .../fusion/composition/tests/loaders.spec.ts | 10 ++++ packages/include/package.json | 1 + packages/include/src/index.ts | 32 ++++++----- packages/legacy/cli/src/index.ts | 1 + packages/legacy/transforms/hive/src/index.ts | 54 ++++++++++++++----- packages/legacy/utils/src/in-context-sdk.ts | 28 ++++++++++ packages/loaders/json-schema/src/index.ts | 5 +- packages/loaders/openapi/src/index.ts | 5 +- packages/loaders/raml/src/index.ts | 5 +- yarn.lock | 1 + 15 files changed, 138 insertions(+), 33 deletions(-) create mode 100644 .changeset/@graphql-mesh_include-8007-dependencies.md create mode 100644 .changeset/cuddly-spies-argue.md create mode 100644 .changeset/fresh-insects-give.md create mode 100644 .changeset/silver-days-sort.md create mode 100644 .changeset/thick-goats-type.md diff --git a/.changeset/@graphql-mesh_include-8007-dependencies.md b/.changeset/@graphql-mesh_include-8007-dependencies.md new file mode 100644 index 0000000000000..d5ea00d0fe46e --- /dev/null +++ b/.changeset/@graphql-mesh_include-8007-dependencies.md @@ -0,0 +1,5 @@ +--- +"@graphql-mesh/include": patch +--- +dependencies updates: + - Added dependency [`@graphql-mesh/utils@^0.103.4` ↗︎](https://www.npmjs.com/package/@graphql-mesh/utils/v/0.103.4) (to `dependencies`) diff --git a/.changeset/cuddly-spies-argue.md b/.changeset/cuddly-spies-argue.md new file mode 100644 index 0000000000000..f0acbddad9deb --- /dev/null +++ b/.changeset/cuddly-spies-argue.md @@ -0,0 +1,5 @@ +--- +'@graphql-mesh/transform-hive': patch +--- + +Do not break the request even if the operation is not able to process diff --git a/.changeset/fresh-insects-give.md b/.changeset/fresh-insects-give.md new file mode 100644 index 0000000000000..6c775fed15676 --- /dev/null +++ b/.changeset/fresh-insects-give.md @@ -0,0 +1,8 @@ +--- +'@omnigraph/json-schema': patch +'@omnigraph/openapi': patch +'@omnigraph/raml': patch +--- + +DEBUG logs were accidentially written to the output, now it correctly prints logs to the logger not +the output diff --git a/.changeset/silver-days-sort.md b/.changeset/silver-days-sort.md new file mode 100644 index 0000000000000..ac581adcc5f94 --- /dev/null +++ b/.changeset/silver-days-sort.md @@ -0,0 +1,6 @@ +--- +'@graphql-mesh/cli': patch +'@graphql-mesh/include': patch +--- + +Improvements on imports diff --git a/.changeset/thick-goats-type.md b/.changeset/thick-goats-type.md new file mode 100644 index 0000000000000..c404e134d21c4 --- /dev/null +++ b/.changeset/thick-goats-type.md @@ -0,0 +1,5 @@ +--- +'@graphql-mesh/utils': patch +--- + +Always pass a valid info diff --git a/packages/fusion/composition/tests/loaders.spec.ts b/packages/fusion/composition/tests/loaders.spec.ts index 10f282a19ae87..559e4bbc658b5 100644 --- a/packages/fusion/composition/tests/loaders.spec.ts +++ b/packages/fusion/composition/tests/loaders.spec.ts @@ -1,9 +1,18 @@ import { OperationTypeNode } from 'graphql'; import { createGatewayRuntime, useCustomFetch } from '@graphql-hive/gateway-runtime'; +import type { Logger } from '@graphql-mesh/types'; import { loadJSONSchemaSubgraph } from '@omnigraph/json-schema'; import { composeSubgraphs } from '../src/compose'; describe('Loaders', () => { + const logger: Logger = { + log: jest.fn(), + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + child: () => logger, + }; it('works', async () => { const loadedSubgraph = loadJSONSchemaSubgraph('TEST', { endpoint: 'http://localhost/my-test-api', @@ -24,6 +33,7 @@ describe('Loaders', () => { })({ fetch, cwd: process.cwd(), + logger, }); const { supergraphSdl } = composeSubgraphs([ { diff --git a/packages/include/package.json b/packages/include/package.json index 766d7e742bff3..eac9666440ba8 100644 --- a/packages/include/package.json +++ b/packages/include/package.json @@ -46,6 +46,7 @@ }, "typings": "dist/typings/index.d.ts", "dependencies": { + "@graphql-mesh/utils": "^0.103.4", "dotenv": "^16.3.1", "get-tsconfig": "^4.7.6", "jiti": "^2.0.0", diff --git a/packages/include/src/index.ts b/packages/include/src/index.ts index 3cb5d3cd4e0b4..988fb547bf32b 100644 --- a/packages/include/src/index.ts +++ b/packages/include/src/index.ts @@ -1,9 +1,10 @@ // eslint-disable-next-line import/no-nodejs-modules import Module from 'node:module'; import { createPathsMatcher, getTsconfig } from 'get-tsconfig'; -import createJITI from 'jiti'; +import { createJiti } from 'jiti'; +import { defaultImportFn } from '@graphql-mesh/utils'; -const jiti = createJITI( +const jiti = createJiti( /** * We intentionally provide an empty string here and let jiti handle the base URL. * @@ -11,6 +12,9 @@ const jiti = createJITI( * and `__filename` is not available in ESM. */ '', + { + debug: !!process.env.DEBUG, + }, ); /** @@ -20,18 +24,20 @@ const jiti = createJITI( * * If the module at {@link path} is not found, `null` will be returned. */ -export async function include(path: string, nativeImport?: boolean): Promise { - const module = await (nativeImport ? import(path) : jiti.import(path, {})); - if (!module) { - throw new Error('Included module is empty'); - } - if (typeof module !== 'object') { - throw new Error(`Included module is not an object, is instead "${typeof module}"`); - } - if ('default' in module) { - return module.default as T; +export async function include(path: string): Promise { + try { + // JITI's tryNative tries native at first but with \`import\` + // So in CJS, this becomes \`require\`, but it still satisfies JITI's native import + return await defaultImportFn(path); + } catch { + const mod = await jiti.import(path, { + default: true, + }); + if (!mod) { + throw new Error(`Module at path "${path}" not found`); + } + return mod; } - return module as T; } export interface RegisterTsconfigPathsOptions { diff --git a/packages/legacy/cli/src/index.ts b/packages/legacy/cli/src/index.ts index 83778d7a70108..1ca81f57e085c 100644 --- a/packages/legacy/cli/src/index.ts +++ b/packages/legacy/cli/src/index.ts @@ -124,6 +124,7 @@ export async function graphqlMesh( configName: cliParams.configName, additionalPackagePrefixes: cliParams.additionalPackagePrefixes, initialLoggerPrefix: cliParams.initialLoggerPrefix, + importFn: include, }); logger = meshConfig.logger; diff --git a/packages/legacy/transforms/hive/src/index.ts b/packages/legacy/transforms/hive/src/index.ts index a3c8584967b72..b137169855caf 100644 --- a/packages/legacy/transforms/hive/src/index.ts +++ b/packages/legacy/transforms/hive/src/index.ts @@ -1,20 +1,21 @@ -import type { ExecutionResult, GraphQLSchema } from 'graphql'; +import { isSchema, Kind, visit, type ExecutionResult, type GraphQLSchema } from 'graphql'; import type { HiveClient, HivePluginOptions } from '@graphql-hive/core'; import { createHive } from '@graphql-hive/yoga'; import { process } from '@graphql-mesh/cross-helpers'; import { stringInterpolator } from '@graphql-mesh/string-interpolation'; import type { MeshTransform, MeshTransformOptions, YamlConfig } from '@graphql-mesh/types'; import type { DelegationContext } from '@graphql-tools/delegate'; -import type { ExecutionRequest } from '@graphql-tools/utils'; +import { mapMaybePromise, type ExecutionRequest } from '@graphql-tools/utils'; interface TransformationContext { - collectUsageCallback: ReturnType; + collectUsageCallback?: ReturnType; request: ExecutionRequest; } export default class HiveTransform implements MeshTransform { private hiveClient: HiveClient; private logger: MeshTransformOptions['logger']; + private schema: GraphQLSchema; constructor({ config, pubsub, logger }: MeshTransformOptions) { this.logger = logger; const enabled = @@ -76,19 +77,31 @@ export default class HiveTransform implements MeshTransform { agent, usage, reporting, - autoDispose: ['SIGINT', 'SIGTERM'], + autoDispose: false, selfHosting: config.selfHosting, }); const id = pubsub.subscribe('destroy', () => { - this.hiveClient - .dispose() - .catch(e => logger.error(`Hive client failed to dispose`, e)) - .finally(() => pubsub.unsubscribe(id)); + try { + mapMaybePromise( + this.hiveClient.dispose(), + () => { + pubsub.unsubscribe(id); + }, + e => { + logger.error(`Hive client failed to dispose`, e); + pubsub.unsubscribe(id); + }, + ); + } catch (e) { + logger.error(`Failed to dispose hive client`, e); + pubsub.unsubscribe(id); + } }); } transformSchema(schema: GraphQLSchema) { this.hiveClient.reportSchema({ schema }); + this.schema = schema; return schema; } @@ -97,8 +110,12 @@ export default class HiveTransform implements MeshTransform { delegationContext: DelegationContext, transformationContext: TransformationContext, ) { - transformationContext.collectUsageCallback = this.hiveClient.collectUsage(); - transformationContext.request = request; + try { + transformationContext.collectUsageCallback = this.hiveClient.collectUsage(); + transformationContext.request = request; + } catch (e) { + this.logger.error(`Failed to collect usage`, e); + } return request; } @@ -110,10 +127,19 @@ export default class HiveTransform implements MeshTransform { // eslint-disable-next-line @typescript-eslint/no-floating-promises -- we dont really care about usage reporting result try { transformationContext - .collectUsageCallback( + .collectUsageCallback?.( { - schema: delegationContext.transformedSchema, - document: transformationContext.request.document, + schema: this.schema, + document: visit(transformationContext.request.document, { + [Kind.FIELD](node) { + if (!node.arguments) { + return { + ...node, + arguments: [], + }; + } + }, + }), rootValue: transformationContext.request.rootValue, contextValue: transformationContext.request.context, variableValues: transformationContext.request.variables, @@ -121,7 +147,7 @@ export default class HiveTransform implements MeshTransform { }, result, ) - .catch(e => { + ?.catch(e => { this.logger.error(`Failed to report usage`, e); }); } catch (e) { diff --git a/packages/legacy/utils/src/in-context-sdk.ts b/packages/legacy/utils/src/in-context-sdk.ts index 5b038c70a01bb..03e7a98df02c2 100644 --- a/packages/legacy/utils/src/in-context-sdk.ts +++ b/packages/legacy/utils/src/in-context-sdk.ts @@ -1,4 +1,5 @@ import type { + ArgumentNode, DocumentNode, FieldNode, GraphQLObjectType, @@ -6,6 +7,7 @@ import type { GraphQLSchema, OperationDefinitionNode, OperationTypeNode, + SelectionNode, SelectionSetNode, } from 'graphql'; import { getNamedType, isLeafType, Kind, print } from 'graphql'; @@ -219,6 +221,7 @@ export function getInContextSDK( onDelegateHook => onDelegateHook(onDelegatePayload), onDelegateHookDones, ); + fixInfo(batchDelegationOptions.info, operationType as OperationTypeNode); return mapMaybePromise(onDelegateResult$, () => handleIterationResult( batchDelegateToSchema, @@ -253,6 +256,7 @@ export function getInContextSDK( onDelegateHook => onDelegateHook(onDelegatePayload), onDelegateHookDones, ); + fixInfo(regularDelegateOptions.info, operationType as OperationTypeNode); return mapMaybePromise(onDelegateResult$, () => handleIterationResult( delegateToSchema, @@ -333,3 +337,27 @@ function handleIterationResult any>( return mapMaybePromise(onDelegateDoneResult$, () => delegationResult); }); } + +function fixInfo(info: GraphQLResolveInfo, operationType: OperationTypeNode) { + (info.operation as OperationDefinitionNode) ||= { + kind: Kind.OPERATION_DEFINITION, + operation: operationType as OperationTypeNode, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [], + }, + }; + (info.operation.selectionSet as SelectionSetNode) ||= { + kind: Kind.SELECTION_SET, + selections: [], + }; + info.operation.selectionSet.selections ||= []; + (info.operation.selectionSet.selections[0] as SelectionNode) ||= { + kind: Kind.FIELD, + name: { + kind: Kind.NAME, + value: '__typename', + }, + } as FieldNode; + ((info.operation.selectionSet.selections[0] as FieldNode).arguments as ArgumentNode[]) ||= []; +} diff --git a/packages/loaders/json-schema/src/index.ts b/packages/loaders/json-schema/src/index.ts index 5f3d50f3b9b76..e78c6c958a496 100644 --- a/packages/loaders/json-schema/src/index.ts +++ b/packages/loaders/json-schema/src/index.ts @@ -1,6 +1,6 @@ import type { TransportGetSubgraphExecutor } from '@graphql-mesh/transport-common'; import transportRest, { type RESTTransportOptions } from '@graphql-mesh/transport-rest'; -import type { MeshFetch } from '@graphql-mesh/types'; +import type { Logger, MeshFetch } from '@graphql-mesh/types'; import { loadGraphQLSchemaFromJSONSchemas, loadNonExecutableGraphQLSchemaFromJSONSchemas, @@ -15,12 +15,13 @@ export * from './getGraphQLSchemaFromDereferencedJSONSchema.js'; export type * from './types.js'; export function loadJSONSchemaSubgraph(name: string, options: JSONSchemaLoaderOptions) { - return (ctx: { fetch: MeshFetch; cwd: string }) => ({ + return (ctx: { fetch: MeshFetch; cwd: string; logger: Logger }) => ({ name, schema$: loadNonExecutableGraphQLSchemaFromJSONSchemas(name, { ...options, fetch: ctx.fetch, cwd: ctx.cwd, + logger: ctx.logger, }), }); } diff --git a/packages/loaders/openapi/src/index.ts b/packages/loaders/openapi/src/index.ts index f07c28c7cb087..8d2e5bbd5a65c 100644 --- a/packages/loaders/openapi/src/index.ts +++ b/packages/loaders/openapi/src/index.ts @@ -1,4 +1,4 @@ -import type { MeshFetch } from '@graphql-mesh/types'; +import type { Logger, MeshFetch } from '@graphql-mesh/types'; import { loadNonExecutableGraphQLSchemaFromOpenAPI } from './loadGraphQLSchemaFromOpenAPI.js'; import type { OpenAPILoaderOptions } from './types.js'; @@ -8,12 +8,13 @@ export { getJSONSchemaOptionsFromOpenAPIOptions } from './getJSONSchemaOptionsFr export type { OpenAPILoaderOptions } from './types.js'; export function loadOpenAPISubgraph(name: string, options: OpenAPILoaderOptions) { - return (ctx: { fetch: MeshFetch; cwd: string }) => ({ + return (ctx: { fetch: MeshFetch; cwd: string; logger: Logger }) => ({ name, schema$: loadNonExecutableGraphQLSchemaFromOpenAPI(name, { ...options, fetch: ctx.fetch, cwd: ctx.cwd, + logger: ctx.logger, }), }); } diff --git a/packages/loaders/raml/src/index.ts b/packages/loaders/raml/src/index.ts index 4840db873c0f7..78b1b86c4ed3a 100644 --- a/packages/loaders/raml/src/index.ts +++ b/packages/loaders/raml/src/index.ts @@ -1,4 +1,4 @@ -import type { MeshFetch } from '@graphql-mesh/types'; +import type { Logger, MeshFetch } from '@graphql-mesh/types'; import { loadGraphQLSchemaFromRAML } from './loadGraphQLSchemaFromRAML.js'; import type { RAMLLoaderOptions } from './types.js'; @@ -8,12 +8,13 @@ export { getJSONSchemaOptionsFromRAMLOptions } from './getJSONSchemaOptionsFromR export type { RAMLLoaderOptions } from './types.js'; export function loadRAMLSubgraph(name: string, options: RAMLLoaderOptions) { - return (ctx: { fetch: MeshFetch; cwd: string }) => ({ + return (ctx: { fetch: MeshFetch; cwd: string; logger: Logger }) => ({ name, schema$: loadGraphQLSchemaFromRAML(name, { ...options, fetch: ctx.fetch, cwd: ctx.cwd, + logger: ctx.logger, }), }); } diff --git a/yarn.lock b/yarn.lock index 611c16e745316..72597cf0876cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6199,6 +6199,7 @@ __metadata: version: 0.0.0-use.local resolution: "@graphql-mesh/include@workspace:packages/include" dependencies: + "@graphql-mesh/utils": "npm:^0.103.4" dotenv: "npm:^16.3.1" get-tsconfig: "npm:^4.7.6" glob: "npm:^11.0.0"