Gql direct execution - Improvements (#18972)

#### Direct Execution  __typename & null backfill
##### __typename filling
The direct execution path now correctly derives __typename at every
level of the GraphQL response:
Connection types — CompanyConnection, CompanyEdge, Company, PageInfo
GroupBy types — TaskGroupByConnection (was incorrectly producing
TaskConnection)
Composite fields — Links, FullName, Currency, etc. handled by a
dedicated formatter (was inheriting the parent object's typename)
Previously, __typename was derived from the object's universal
identifier (a UUID), producing broken values like
20202020B3744779A56180086Cb2E17FConnection.
##### Null backfill
Selected fields missing from the resolver result are backfilled with
null, matching the standard Yoga schema behavior.
##### Integration test
A new test runs the same findMany query (with __typename at all
structural levels) through both paths — standard Yoga schema and direct
execution — and asserts identical output via toStrictEqual.
This commit is contained in:
Etienne
2026-03-30 17:20:25 +02:00
committed by GitHub
parent 2a5aab0c44
commit 3d1c53ec9d
15 changed files with 611 additions and 63 deletions
@@ -1,9 +1,15 @@
import { DataArgProcessorService } from 'src/engine/api/common/common-args-processors/data-arg-processor/data-arg-processor.service';
import { FilterArgProcessorService } from 'src/engine/api/common/common-args-processors/filter-arg-processor/filter-arg-processor.service';
import { GroupByArgProcessorService } from 'src/engine/api/common/common-args-processors/group-by-arg-processor/group-by-arg-processor.service';
import { OrderByArgProcessorService } from 'src/engine/api/common/common-args-processors/order-by-arg-processor/order-by-arg-processor.service';
import { OrderByWithGroupByArgProcessorService } from 'src/engine/api/common/common-args-processors/order-by-with-group-by-arg-processor/order-by-with-group-by-arg-processor.service';
import { QueryRunnerArgsFactory } from 'src/engine/api/common/common-args-processors/query-runner-args.factory';
export const CommonArgsProcessors = [
DataArgProcessorService,
FilterArgProcessorService,
GroupByArgProcessorService,
OrderByArgProcessorService,
OrderByWithGroupByArgProcessorService,
QueryRunnerArgsFactory,
]; // TODO: Refacto-common Remove QueryRunnerArgsFactory
@@ -1,9 +1,10 @@
import { isString } from '@sniptt/guards';
import { isNullEquivalentArrayFieldValue } from 'src/engine/api/common/common-args-processors/data-arg-processor/utils/is-null-equivalent-array-field-value.util';
export const transformArrayField = (
value: string | string[] | null,
): string[] | null => {
if (typeof value === 'string') return [value];
if (isString(value)) return [value];
return isNullEquivalentArrayFieldValue(value) ? null : value;
};
@@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import {
ObjectRecordGroupByForAtomicField,
ObjectRecordGroupByForCompositeField,
ObjectRecordGroupByForDateField,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
@Injectable()
export class GroupByArgProcessorService {
process({
groupBy,
}: {
groupBy:
| ObjectRecordGroupByForAtomicField
| ObjectRecordGroupByForCompositeField
| ObjectRecordGroupByForDateField
| Array<
| ObjectRecordGroupByForAtomicField
| ObjectRecordGroupByForCompositeField
| ObjectRecordGroupByForDateField
>;
}): Array<
| ObjectRecordGroupByForAtomicField
| ObjectRecordGroupByForCompositeField
| ObjectRecordGroupByForDateField
> {
if (Array.isArray(groupBy)) {
return groupBy;
}
return [groupBy];
}
}
@@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import {
ObjectRecordOrderByForCompositeField,
ObjectRecordOrderByForScalarField,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@Injectable()
export class OrderByArgProcessorService {
process({
orderBy,
}: {
orderBy:
| undefined
| ObjectRecordOrderByForScalarField
| ObjectRecordOrderByForCompositeField
| ObjectRecordOrderBy;
}): ObjectRecordOrderBy | undefined {
if (Array.isArray(orderBy) || !isDefined(orderBy)) {
return orderBy;
}
return [orderBy];
}
}
@@ -0,0 +1,32 @@
import { Injectable } from '@nestjs/common';
import {
OrderByWithGroupBy,
type AggregateOrderByWithGroupByField,
type ObjectRecordOrderByForCompositeField,
type ObjectRecordOrderByForRelationField,
type ObjectRecordOrderByForScalarField,
type ObjectRecordOrderByWithGroupByDateField,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@Injectable()
export class OrderByWithGroupByArgProcessorService {
process({
orderBy,
}: {
orderBy:
| undefined
| ObjectRecordOrderByForScalarField
| ObjectRecordOrderByForCompositeField
| ObjectRecordOrderByWithGroupByDateField
| ObjectRecordOrderByForRelationField
| AggregateOrderByWithGroupByField
| OrderByWithGroupBy;
}): OrderByWithGroupBy | undefined {
if (Array.isArray(orderBy) || !isDefined(orderBy)) {
return orderBy;
}
return [orderBy];
}
}