Remove direct execution feature flag - WIP (#19254)

Bug fixes exposed by always-on direct execution
1. GraphQL spec compliance — data[field] = null on resolver error
direct-execution.service.ts — Changed from Promise.allSettled (which
lost the responseKey on rejection) to Promise.all with per-field
try/catch; errors now set data[responseKey] = null per spec
2. Empty object arguments skipped (extractArgumentsFromAst)
extract-arguments-from-ast.util.ts — Removed isEmptyObject check;
filter: {}, data: {} now correctly passed to resolvers instead of
silently dropped (which caused permissions to never be checked)
3. orderBy: {} factory default treated as "no ordering"
direct-execution.service.ts — Before calling the resolver, strips
orderBy: {} and orderByForRecords: {} (empty-object factory defaults
that mean "no ordering")
assert-find-many-args.util.ts / assert-group-by-args.util.ts — Accept {}
for orderBy without throwing
4. orderBy: { field: '...' } object auto-coerced to [{ field: '...' }]
array
direct-execution.service.ts — Applies GraphQL list coercion: a
non-array, non-empty orderBy object is wrapped in an array before
assertion and resolver call
5. totalCount and aggregate fields returned as strings from PostgreSQL
graphql-format-result-from-selected-fields.util.ts — Added
coerceAggregateValue that parses numeric strings to numbers for
totalCount, sum*, avg*, min*, max*, count*, percentageOf* fields
Test updates
nested-relation-queries.integration-spec.ts — Updated expected error
message from Yoga schema-validation message to direct execution resolver
message
~30 snapshot files — Updated to reflect direct execution's error
messages (different from Yoga schema-validation messages for input type
errors)
This commit is contained in:
Etienne
2026-04-03 17:23:01 +02:00
committed by GitHub
parent 90597e47ca
commit d562a384c2
102 changed files with 647 additions and 629 deletions
@@ -59,13 +59,14 @@ export class CreateManyRecordsService {
createdBy: actorMetadata,
}));
const createdRecords = await this.commonCreateManyRunner.execute(
{
data: cleanedRecords,
selectedFields,
},
queryRunnerContext,
);
const { results: createdRecords } =
await this.commonCreateManyRunner.execute(
{
data: cleanedRecords,
selectedFields,
},
queryRunnerContext,
);
this.logger.log(
`Created ${createdRecords.length} records in ${objectName}`,
@@ -61,13 +61,14 @@ export class CreateRecordService {
const cleanedRecord = removeUndefinedFromRecord(objectRecord);
const dataWithActor = { ...cleanedRecord, createdBy: actorMetadata };
const createdRecord = await this.commonCreateOneRunner.execute(
{
data: dataWithActor,
selectedFields,
},
queryRunnerContext,
);
const { results: createdRecord } =
await this.commonCreateOneRunner.execute(
{
data: dataWithActor,
selectedFields,
},
queryRunnerContext,
);
this.logger.log(`Record created successfully in ${objectName}`);
@@ -54,13 +54,14 @@ export class DeleteRecordService {
}
if (soft) {
const deletedRecord = await this.commonDeleteOneRunner.execute(
{
id: objectRecordId,
selectedFields,
},
queryRunnerContext,
);
const { results: deletedRecord } =
await this.commonDeleteOneRunner.execute(
{
id: objectRecordId,
selectedFields,
},
queryRunnerContext,
);
this.logger.log(`Record soft deleted successfully from ${objectName}`);
@@ -70,13 +71,14 @@ export class DeleteRecordService {
result: deletedRecord,
};
} else {
const destroyedRecord = await this.commonDestroyOneRunner.execute(
{
id: objectRecordId,
selectedFields,
},
queryRunnerContext,
);
const { results: destroyedRecord } =
await this.commonDestroyOneRunner.execute(
{
id: objectRecordId,
selectedFields,
},
queryRunnerContext,
);
this.logger.log(
`Record permanently deleted successfully from ${objectName}`,
@@ -50,7 +50,9 @@ export class FindRecordsService {
{ id: OrderByDirection.AscNullsFirst },
];
const { records, totalCount } = await this.commonFindManyRunner.execute(
const {
results: { records, totalCount },
} = await this.commonFindManyRunner.execute(
{
filter,
orderBy: orderByWithIdCondition,
@@ -50,10 +50,11 @@ export class UpdateManyRecordsService {
const cleanedData = removeUndefinedFromRecord(data);
const updatedRecords = await this.commonUpdateManyRunner.execute(
{ filter, data: cleanedData, selectedFields },
queryRunnerContext,
);
const { results: updatedRecords } =
await this.commonUpdateManyRunner.execute(
{ filter, data: cleanedData, selectedFields },
queryRunnerContext,
);
this.logger.log(
`Updated ${updatedRecords.length} records in ${objectName}`,
@@ -89,14 +89,15 @@ export class UpdateRecordService {
// This prevents validation errors for partial composite field inputs
const cleanedRecord = removeUndefinedFromRecord(filteredObjectRecord);
const updatedRecord = await this.commonUpdateOneRunner.execute(
{
id: objectRecordId,
data: cleanedRecord,
selectedFields,
},
queryRunnerContext,
);
const { results: updatedRecord } =
await this.commonUpdateOneRunner.execute(
{
id: objectRecordId,
data: cleanedRecord,
selectedFields,
},
queryRunnerContext,
);
this.logger.log(`Record updated successfully in ${objectName}`);
@@ -48,14 +48,15 @@ export class UpsertRecordService {
const cleanedRecord = removeUndefinedFromRecord(objectRecord);
// Use Common API with upsert flag - it handles conflict detection automatically
const upsertedRecord = await this.commonCreateOneRunner.execute(
{
data: cleanedRecord,
selectedFields,
upsert: true,
},
queryRunnerContext,
);
const { results: upsertedRecord } =
await this.commonCreateOneRunner.execute(
{
data: cleanedRecord,
selectedFields,
upsert: true,
},
queryRunnerContext,
);
this.logger.log(`Record upserted successfully in ${objectName}`);