Gql direct execution - Handle introspection queries (#19219)

### Context
GraphQL introspection queries (__schema, __type) were going through the
full Yoga server pipeline, which forces a complete workspace schema
build, loading flat metadata maps, building all GraphQL types, wiring
all resolver factories, and calling makeExecutableSchema. This is
expensive, even though introspection only needs the type structure and
zero resolver execution.

A new WorkspaceGraphqlSchemaSDLService extracts the SDL computation that
was previously embedded inside WorkspaceSchemaFactory.

From `direct-execution.service.ts` : 
- `buildSchema(sdl)` reconstructs a resolver-free GraphQLSchema from the
SDL
     - pure CPU, not cached, not sure it worths it ?

- `execute({ schema, document, variableValues })` from graphql-js,
introspection is answered entirely by the graphql-js runtime from type
metadata, no resolver execution needed


#### Nice to do ?
- Use new cache service for typeDefs ?


### Renaming bonus: `typeDefs` → `sdl`

`typeDefs` is an Apollo/graphql-tools convention. It's the parameter
name in
`makeExecutableSchema({ typeDefs, resolvers })`, not a native GraphQL
spec term.

In proper GraphQL semantics, what this service produces is the **SDL**
(Schema
Definition Language): the official term for the string representation of
a schema.
`printSchema()` produces it, `buildSchema()` consumes it.

##### Why the distinction matters

- **Type definitions** implies partial type declarations (objects,
scalars, enums…)
- **Schema SDL** conveys a *complete* schema document: all types
**plus** the root
operation types (`Query`, `Mutation`) which is exactly what
`printSchema(schema)`
  produces
This commit is contained in:
Etienne
2026-04-02 13:50:18 +02:00
committed by GitHub
parent 579714b62f
commit a303d9ca1b
17 changed files with 658 additions and 612 deletions
@@ -4,6 +4,8 @@ import { CoreCommonApiModule } from 'src/engine/api/common/core-common-api.modul
import { DirectExecutionService } from 'src/engine/api/graphql/direct-execution/direct-execution.service';
import { WorkspaceResolverNameMapCacheService } from 'src/engine/api/graphql/direct-execution/services/workspace-resolver-name-map-cache.service';
import { WorkspaceResolverBuilderModule } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module';
import { WorkspaceGraphqlSchemaSDLModule } from 'src/engine/api/graphql/workspace-graphql-schema-sdl/workspace-graphql-schema-sdl.module';
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
@@ -13,6 +15,8 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
WorkspaceManyOrAllFlatEntityMapsCacheModule,
WorkspaceCacheModule,
WorkspaceResolverBuilderModule,
WorkspaceGraphqlSchemaSDLModule,
MetricsModule,
],
providers: [DirectExecutionService, WorkspaceResolverNameMapCacheService],
exports: [DirectExecutionService],
@@ -4,6 +4,8 @@ import { type MessageDescriptor } from '@lingui/core';
import { type Request } from 'express';
import {
GraphQLError,
buildSchema,
execute,
type DocumentNode,
type FieldNode,
type GraphQLFormattedError,
@@ -40,6 +42,7 @@ import { graphQLBuildFragmentMap } from 'src/engine/api/graphql/direct-execution
import { graphQLBuildPartialResolveInfo } from 'src/engine/api/graphql/direct-execution/utils/graphql-build-partial-resolve-info.util';
import { graphQLExtractTopLevelFields } from 'src/engine/api/graphql/direct-execution/utils/graphql-extract-top-level-fields.util';
import { graphQLFormatResultFromSelectedFields } from 'src/engine/api/graphql/direct-execution/utils/graphql-format-result-from-selected-fields.util';
import { WorkspaceGraphqlSchemaSDLService } from 'src/engine/api/graphql/workspace-graphql-schema-sdl/workspace-graphql-schema-sdl.service';
import { ResolverOutput } from 'src/engine/api/graphql/workspace-query-runner/interfaces/resolver-output';
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@@ -62,6 +65,8 @@ import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/gr
import { type WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util';
@@ -84,8 +89,10 @@ export class DirectExecutionService {
constructor(
private readonly workspaceFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceGraphqlSchemaSDLService: WorkspaceGraphqlSchemaSDLService,
private readonly twentyConfigService: TwentyConfigService,
private readonly i18nService: I18nService,
private readonly metricsService: MetricsService,
private readonly findManyResolverFactory: FindManyResolverFactory,
private readonly findOneResolverFactory: FindOneResolverFactory,
private readonly findDuplicatesResolverFactory: FindDuplicatesResolverFactory,
@@ -144,7 +151,7 @@ export class DirectExecutionService {
]);
}
async getGeneratedWorkspaceResolverNames(
async getWorkspaceResolverNames(
workspaceId: string,
): Promise<Set<string> | null> {
const { graphQLResolverNameMap } =
@@ -158,6 +165,25 @@ export class DirectExecutionService {
async execute(
req: Request,
document: DocumentNode,
hasIntrospectionFields: boolean,
hasWorkspaceFields: boolean,
): Promise<DirectExecutionResult | null> {
const [introspectionResult, workspaceResult] = await Promise.all([
hasIntrospectionFields
? this.executeIntrospectionQuery(req, document)
: null,
hasWorkspaceFields ? this.executeWorkspaceQuery(req, document) : null,
]);
return this.mergeDirectExecutionResults(
introspectionResult,
workspaceResult,
);
}
private async executeWorkspaceQuery(
req: Request,
document: DocumentNode,
): Promise<DirectExecutionResult | null> {
try {
const workspaceId = req.workspace?.id;
@@ -255,6 +281,69 @@ export class DirectExecutionService {
}
}
private async executeIntrospectionQuery(
req: Request,
document: DocumentNode,
): Promise<DirectExecutionResult | null> {
try {
if (!isDefined(req.workspace)) {
return null;
}
const schemaSDLResult =
await this.workspaceGraphqlSchemaSDLService.getOrComputeSchemaSDL(
req.workspace,
req.application?.id ?? undefined,
);
if (!isDefined(schemaSDLResult)) {
return null;
}
const schema = buildSchema(schemaSDLResult.sdl);
const result = await execute({
schema,
document,
operationName: req.body?.operationName as string | undefined,
variableValues: (req.body?.variables as Record<string, unknown>) ?? {},
});
await this.metricsService.incrementCounter({
key: MetricsKeys.GraphqlIntrospectionDirectExecution,
shouldStoreInCache: false,
});
return {
data: result.data as Record<string, unknown> | undefined,
errors: result.errors?.map((error) => error.toJSON()),
};
} catch (error) {
return { errors: [this.formatError(error, req)] };
}
}
private mergeDirectExecutionResults(
introspectionResult: DirectExecutionResult | null,
workspaceResult: DirectExecutionResult | null,
): DirectExecutionResult | null {
if (!introspectionResult && !workspaceResult) {
return null;
}
const errors = [
...(introspectionResult?.errors ?? []),
...(workspaceResult?.errors ?? []),
];
return {
data: {
...introspectionResult?.data,
...workspaceResult?.data,
},
...(errors.length > 0 ? { errors } : {}),
};
}
private async executeField({
entry,
args,
@@ -5,9 +5,8 @@ import { FeatureFlagKey } from 'twenty-shared/types';
import { isNull } from '@sniptt/guards';
import { type DirectExecutionService } from 'src/engine/api/graphql/direct-execution/direct-execution.service';
import { computeSkipWorkspaceSchemaCreation } from 'src/engine/api/graphql/direct-execution/utils/compute-skip-workspace-schema-creation.util';
import { classifyTopLevelFields } from 'src/engine/api/graphql/direct-execution/utils/classify-top-level-fields.util';
import { findOperationDefinition } from 'src/engine/api/graphql/direct-execution/utils/find-operation-definition.util';
import { hasOnlyGeneratedWorkspaceResolvers } from 'src/engine/api/graphql/direct-execution/utils/has-only-generated-workspace-resolvers.util';
import { isSubscriptionOperation } from 'src/engine/api/graphql/direct-execution/utils/is-subscription-operation.util';
import { type FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
@@ -37,15 +36,6 @@ export function useDirectExecution(
return;
}
const generatedWorkspaceResolverNames =
await config.directExecutionService.getGeneratedWorkspaceResolverNames(
req.workspace.id,
);
if (!generatedWorkspaceResolverNames) {
return;
}
const queryString = req.body.query as string;
const operationName = req.body.operationName as string | undefined;
@@ -63,28 +53,32 @@ export function useDirectExecution(
return;
}
if (
computeSkipWorkspaceSchemaCreation(
queryString,
document,
operationName,
generatedWorkspaceResolverNames,
)
) {
req.skipWorkspaceSchemaCreation = true;
}
const workspaceResolverNames =
await config.directExecutionService.getWorkspaceResolverNames(
req.workspace.id,
);
if (
!hasOnlyGeneratedWorkspaceResolvers(
document,
operationName,
generatedWorkspaceResolverNames,
)
) {
if (!workspaceResolverNames) {
return;
}
const result = await config.directExecutionService.execute(req, document);
const { hasIntrospectionFields, hasWorkspaceFields, hasCoreFields } =
classifyTopLevelFields(document, operationName, workspaceResolverNames);
if (!hasCoreFields) {
req.skipWorkspaceSchemaCreation = true;
}
if (hasCoreFields) {
return;
}
const result = await config.directExecutionService.execute(
req,
document,
hasIntrospectionFields,
hasWorkspaceFields,
);
if (isNull(result)) {
return;
@@ -0,0 +1,169 @@
import { parse } from 'graphql';
import { classifyTopLevelFields } from 'src/engine/api/graphql/direct-execution/utils/classify-top-level-fields.util';
const WORKSPACE_RESOLVERS = new Set([
'findManyCompanies',
'findOneCompany',
'createOneCompany',
'findManyPeople',
'findOnePerson',
]);
describe('classifyTopLevelFields', () => {
it('should classify a pure introspection query', () => {
const query = `
query {
__schema { types { name } }
__type(name: "Company") { name }
}
`;
expect(
classifyTopLevelFields(parse(query), undefined, WORKSPACE_RESOLVERS),
).toEqual({
hasIntrospectionFields: true,
hasWorkspaceFields: false,
hasCoreFields: false,
});
});
it('should classify a pure workspace query', () => {
const query = `
query {
findManyCompanies { id name }
findOnePerson { id }
}
`;
expect(
classifyTopLevelFields(parse(query), undefined, WORKSPACE_RESOLVERS),
).toEqual({
hasIntrospectionFields: false,
hasWorkspaceFields: true,
hasCoreFields: false,
});
});
it('should classify a pure core query', () => {
const query = `
query {
currentWorkspace { id }
currentUser { id }
}
`;
expect(
classifyTopLevelFields(parse(query), undefined, WORKSPACE_RESOLVERS),
).toEqual({
hasIntrospectionFields: false,
hasWorkspaceFields: false,
hasCoreFields: true,
});
});
it('should classify a mixed introspection + workspace query', () => {
const query = `
query {
__schema { types { name } }
findManyCompanies { id }
}
`;
expect(
classifyTopLevelFields(parse(query), undefined, WORKSPACE_RESOLVERS),
).toEqual({
hasIntrospectionFields: true,
hasWorkspaceFields: true,
hasCoreFields: false,
});
});
it('should classify a mixed workspace + core query', () => {
const query = `
query {
findManyCompanies { id }
currentWorkspace { id }
}
`;
expect(
classifyTopLevelFields(parse(query), undefined, WORKSPACE_RESOLVERS),
).toEqual({
hasIntrospectionFields: false,
hasWorkspaceFields: true,
hasCoreFields: true,
});
});
it('should not classify __typename as introspection', () => {
const query = `
query {
currentWorkspace { id __typename }
}
`;
expect(
classifyTopLevelFields(parse(query), undefined, WORKSPACE_RESOLVERS),
).toEqual({
hasIntrospectionFields: false,
hasWorkspaceFields: false,
hasCoreFields: true,
});
});
it('should expand fragment spreads when classifying', () => {
const query = `
query {
__schema { types { name } }
...WorkspaceFragment
}
fragment WorkspaceFragment on Query {
findManyCompanies { id }
}
`;
expect(
classifyTopLevelFields(parse(query), undefined, WORKSPACE_RESOLVERS),
).toEqual({
hasIntrospectionFields: true,
hasWorkspaceFields: true,
hasCoreFields: false,
});
});
it('should respect operationName', () => {
const query = `
query IntrospectionQuery {
__schema { types { name } }
}
query WorkspaceQuery {
findManyCompanies { id }
}
`;
expect(
classifyTopLevelFields(
parse(query),
'IntrospectionQuery',
WORKSPACE_RESOLVERS,
),
).toEqual({
hasIntrospectionFields: true,
hasWorkspaceFields: false,
hasCoreFields: false,
});
expect(
classifyTopLevelFields(
parse(query),
'WorkspaceQuery',
WORKSPACE_RESOLVERS,
),
).toEqual({
hasIntrospectionFields: false,
hasWorkspaceFields: true,
hasCoreFields: false,
});
});
});
@@ -1,178 +0,0 @@
import { parse } from 'graphql';
import { computeSkipWorkspaceSchemaCreation } from 'src/engine/api/graphql/direct-execution/utils/compute-skip-workspace-schema-creation.util';
const GENERATED_RESOLVERS = new Set([
'findManyCompanies',
'findOneCompany',
'createOneCompany',
'findManyPeople',
'findOnePerson',
]);
describe('computeSkipWorkspaceSchemaCreation', () => {
it('should return true when all fields are core resolvers', () => {
const query = `
query {
currentWorkspace { id }
}
`;
expect(
computeSkipWorkspaceSchemaCreation(
query,
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(true);
});
it('should return true for multiple core resolver fields', () => {
const query = `
query {
currentWorkspace { id }
currentUser { id }
}
`;
expect(
computeSkipWorkspaceSchemaCreation(
query,
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(true);
});
it('should return true when all fields are generated workspace resolvers', () => {
const query = `
query {
findManyCompanies { id name }
}
`;
expect(
computeSkipWorkspaceSchemaCreation(
query,
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(true);
});
it('should return false for mixed queries', () => {
const query = `
query {
findManyCompanies { id }
currentWorkspace { id }
}
`;
expect(
computeSkipWorkspaceSchemaCreation(
query,
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(false);
});
it('should return false for __schema introspection', () => {
const query = `
query {
__schema { types { name } }
}
`;
expect(
computeSkipWorkspaceSchemaCreation(
query,
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(false);
});
it('should return false for __type introspection', () => {
const query = `
query {
__type(name: "Company") { name fields { name } }
}
`;
expect(
computeSkipWorkspaceSchemaCreation(
query,
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(false);
});
it('should not treat __typename as introspection', () => {
const query = `
query {
currentWorkspace { id __typename }
}
`;
expect(
computeSkipWorkspaceSchemaCreation(
query,
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(true);
});
it('should return true when no operation matches (no fields to check)', () => {
const query = `
query GetCompanies { findManyCompanies { id } }
`;
expect(
computeSkipWorkspaceSchemaCreation(
query,
parse(query),
'NonExistent',
GENERATED_RESOLVERS,
),
).toBe(true);
});
it('should respect operationName', () => {
const query = `
query CoreQuery {
currentWorkspace { id }
}
query WorkspaceQuery {
findManyCompanies { id }
}
`;
expect(
computeSkipWorkspaceSchemaCreation(
query,
parse(query),
'CoreQuery',
GENERATED_RESOLVERS,
),
).toBe(true);
expect(
computeSkipWorkspaceSchemaCreation(
query,
parse(query),
'WorkspaceQuery',
GENERATED_RESOLVERS,
),
).toBe(true);
});
});
@@ -73,4 +73,39 @@ describe('graphQLExtractTopLevelFields', () => {
expect(fields).toEqual([]);
});
it('should expand fragment spreads at the top level', () => {
const query = `
query {
__schema { types { name } }
...DataFragment
}
fragment DataFragment on Query {
companies { id }
}
`;
const fields = graphQLExtractTopLevelFields(parse(query), undefined);
expect(fields).toHaveLength(2);
expect(fields[0].name.value).toBe('__schema');
expect(fields[1].name.value).toBe('companies');
});
it('should expand inline fragments at the top level', () => {
const query = `
query {
__schema { types { name } }
... on Query {
companies { id }
}
}
`;
const fields = graphQLExtractTopLevelFields(parse(query), undefined);
expect(fields).toHaveLength(2);
expect(fields[0].name.value).toBe('__schema');
expect(fields[1].name.value).toBe('companies');
});
});
@@ -1,121 +0,0 @@
import { parse } from 'graphql';
import { hasOnlyGeneratedWorkspaceResolvers } from 'src/engine/api/graphql/direct-execution/utils/has-only-generated-workspace-resolvers.util';
const GENERATED_RESOLVERS = new Set([
'companies',
'company',
'createOneCompany',
'people',
'person',
]);
describe('hasOnlyGeneratedWorkspaceResolvers', () => {
it('should return true when all fields are generated workspace resolvers', () => {
const query = `
query {
companies { id name }
people { id email }
}
`;
expect(
hasOnlyGeneratedWorkspaceResolvers(
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(true);
});
it('should return true for a single generated workspace resolver', () => {
const query = `
query {
company(filter: { id: { eq: "123" } }) { id }
}
`;
expect(
hasOnlyGeneratedWorkspaceResolvers(
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(true);
});
it('should return false when all fields are core resolvers', () => {
const query = `
query {
search { id }
getTimelineCalendarEventsFromOpportunityId { id }
}
`;
expect(
hasOnlyGeneratedWorkspaceResolvers(
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(false);
});
it('should return false for mixed queries', () => {
const query = `
query {
companies { id }
getTimelineCalendarEventsFromOpportunityId { id }
}
`;
expect(
hasOnlyGeneratedWorkspaceResolvers(
parse(query),
undefined,
GENERATED_RESOLVERS,
),
).toBe(false);
});
it('should return true when no operation matches (no fields to check)', () => {
const query = `
query GetCompanies { companies { id } }
`;
expect(
hasOnlyGeneratedWorkspaceResolvers(
parse(query),
'NonExistent',
GENERATED_RESOLVERS,
),
).toBe(true);
});
it('should respect operationName', () => {
const query = `
query WorkspaceQuery {
companies { id }
}
query CoreQuery {
getTimelineCalendarEventsFromOpportunityId { id }
}
`;
expect(
hasOnlyGeneratedWorkspaceResolvers(
parse(query),
'WorkspaceQuery',
GENERATED_RESOLVERS,
),
).toBe(true);
expect(
hasOnlyGeneratedWorkspaceResolvers(
parse(query),
'CoreQuery',
GENERATED_RESOLVERS,
),
).toBe(false);
});
});
@@ -0,0 +1,35 @@
import { type DocumentNode } from 'graphql';
import { graphQLExtractTopLevelFields } from 'src/engine/api/graphql/direct-execution/utils/graphql-extract-top-level-fields.util';
const INTROSPECTION_FIELD_NAMES = new Set(['__schema', '__type']);
type TopLevelFieldsClassification = {
hasIntrospectionFields: boolean;
hasWorkspaceFields: boolean;
hasCoreFields: boolean;
};
export const classifyTopLevelFields = (
document: DocumentNode,
operationName: string | undefined,
workspaceResolverNames: Set<string>,
): TopLevelFieldsClassification => {
const topLevelFields = graphQLExtractTopLevelFields(document, operationName);
let hasIntrospectionFields = false;
let hasWorkspaceFields = false;
let hasCoreFields = false;
for (const field of topLevelFields) {
if (INTROSPECTION_FIELD_NAMES.has(field.name.value)) {
hasIntrospectionFields = true;
} else if (workspaceResolverNames.has(field.name.value)) {
hasWorkspaceFields = true;
} else {
hasCoreFields = true;
}
}
return { hasIntrospectionFields, hasWorkspaceFields, hasCoreFields };
};
@@ -1,27 +0,0 @@
import { type DocumentNode } from 'graphql';
import { graphQLExtractTopLevelFields } from 'src/engine/api/graphql/direct-execution/utils/graphql-extract-top-level-fields.util';
const INTROSPECTION_PATTERN = /__schema|__type(?!name)/;
export const computeSkipWorkspaceSchemaCreation = (
queryString: string,
document: DocumentNode,
operationName: string | undefined,
generatedWorkspaceResolverNames: Set<string>,
): boolean => {
if (INTROSPECTION_PATTERN.test(queryString)) {
return false;
}
const topLevelFields = graphQLExtractTopLevelFields(document, operationName);
const hasCore = topLevelFields.some(
(field) => !generatedWorkspaceResolverNames.has(field.name.value),
);
const hasGenerated = topLevelFields.some((field) =>
generatedWorkspaceResolverNames.has(field.name.value),
);
return !(hasCore && hasGenerated);
};
@@ -1,6 +1,7 @@
import { type DocumentNode, type FieldNode, Kind } from 'graphql';
import { findOperationDefinition } from 'src/engine/api/graphql/direct-execution/utils/find-operation-definition.util';
import { graphQLBuildFragmentMap } from 'src/engine/api/graphql/direct-execution/utils/graphql-build-fragment-map.util';
export const graphQLExtractTopLevelFields = (
document: DocumentNode,
@@ -12,7 +13,30 @@ export const graphQLExtractTopLevelFields = (
return [];
}
return operationDefinition.selectionSet.selections.filter(
(selection): selection is FieldNode => selection.kind === Kind.FIELD,
);
const fragmentMap = graphQLBuildFragmentMap(document);
const fields: FieldNode[] = [];
for (const selection of operationDefinition.selectionSet.selections) {
if (selection.kind === Kind.FIELD) {
fields.push(selection);
} else if (selection.kind === Kind.FRAGMENT_SPREAD) {
const fragment = fragmentMap.get(selection.name.value);
if (fragment) {
for (const fragmentSelection of fragment.selectionSet.selections) {
if (fragmentSelection.kind === Kind.FIELD) {
fields.push(fragmentSelection);
}
}
}
} else if (selection.kind === Kind.INLINE_FRAGMENT) {
for (const inlineSelection of selection.selectionSet.selections) {
if (inlineSelection.kind === Kind.FIELD) {
fields.push(inlineSelection);
}
}
}
}
return fields;
};
@@ -1,15 +0,0 @@
import { type DocumentNode } from 'graphql';
import { graphQLExtractTopLevelFields } from 'src/engine/api/graphql/direct-execution/utils/graphql-extract-top-level-fields.util';
export const hasOnlyGeneratedWorkspaceResolvers = (
document: DocumentNode,
operationName: string | undefined,
generatedWorkspaceResolverNames: Set<string>,
): boolean => {
const topLevelFields = graphQLExtractTopLevelFields(document, operationName);
return topLevelFields.every((field) =>
generatedWorkspaceResolverNames.has(field.name.value),
);
};