Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ export const deleteUserMutationDocument = \`
mutation DeleteUserMutation($input: DeleteUserInput!) {
deleteUser(input: $input) {
clientMutationId
deletedId
deletedUserNodeId
}
}
\`;
Expand All @@ -782,7 +782,7 @@ export interface DeleteUserMutationVariables {
export interface DeleteUserMutationResult {
deleteUser: {
clientMutationId: string | null;
deletedId: string | null;
deletedUserNodeId: string | null;
};
}
/**
Expand Down Expand Up @@ -834,7 +834,7 @@ export const deletePostMutationDocument = \`
mutation DeletePostMutation($input: DeletePostInput!) {
deletePost(input: $input) {
clientMutationId
deletedId
deletedPostNodeId
}
}
\`;
Expand All @@ -846,7 +846,7 @@ export interface DeletePostMutationVariables {
export interface DeletePostMutationResult {
deletePost: {
clientMutationId: string | null;
deletedId: string | null;
deletedPostNodeId: string | null;
};
}
/**
Expand Down Expand Up @@ -895,7 +895,7 @@ export const deleteUserMutationDocument = \`
mutation DeleteUserMutation($input: DeleteUserInput!) {
deleteUser(input: $input) {
clientMutationId
deletedId
deletedUserNodeId
}
}
\`;
Expand All @@ -907,7 +907,7 @@ export interface DeleteUserMutationVariables {
export interface DeleteUserMutationResult {
deleteUser: {
clientMutationId: string | null;
deletedId: string | null;
deletedUserNodeId: string | null;
};
}
/**
Expand Down
16 changes: 13 additions & 3 deletions graphql/codegen/src/cli/codegen/gql-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
ArgumentNode,
VariableDefinitionNode,
} from 'graphql';
import type { CleanTable, CleanField } from '../../types/schema';
import type { CleanTable, CleanField, TypeRegistry } from '../../types/schema';
import {
getTableNames,
getAllRowsQueryName,
Expand All @@ -26,6 +26,8 @@ import {
getScalarFields,
getPrimaryKeyInfo,
ucFirst,
getDeletedNodeIdFieldName,
getDeletePayloadTypeName,
} from './utils';


Expand Down Expand Up @@ -345,16 +347,24 @@ export function buildUpdateMutationAST(config: UpdateMutationConfig): DocumentNo

export interface DeleteMutationConfig {
table: CleanTable;
/** TypeRegistry for looking up actual payload field names from schema */
typeRegistry?: TypeRegistry;
}

/**
* Build a delete mutation AST for a table
*/
export function buildDeleteMutationAST(config: DeleteMutationConfig): DocumentNode {
const { table } = config;
const { table, typeRegistry } = config;
const { typeName } = getTableNames(table);
const mutationName = getDeleteMutationName(table);
const inputTypeName = `Delete${typeName}Input`;
const deletePayloadTypeName = getDeletePayloadTypeName(table);
const deletedNodeIdFieldName = getDeletedNodeIdFieldName(
typeRegistry,
deletePayloadTypeName,
typeName
);

// Variable definitions
const variableDefinitions: VariableDefinitionNode[] = [
Expand Down Expand Up @@ -383,7 +393,7 @@ export function buildDeleteMutationAST(config: DeleteMutationConfig): DocumentNo
selectionSet: t.selectionSet({
selections: [
t.field({ name: 'clientMutationId' }),
t.field({ name: 'deletedId' }),
t.field({ name: deletedNodeIdFieldName }),
],
}),
}),
Expand Down
1 change: 1 addition & 0 deletions graphql/codegen/src/cli/codegen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export function generate(options: GenerateOptions): GenerateResult {
useCentralizedKeys,
hasRelationships,
tableTypeNames,
typeRegistry: customOperations?.typeRegistry,
});
for (const hook of mutationHooks) {
files.push({
Expand Down
26 changes: 20 additions & 6 deletions graphql/codegen/src/cli/codegen/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* useUpdateCarMutation.ts
* useDeleteCarMutation.ts
*/
import type { CleanTable } from '../../types/schema';
import type { CleanTable, TypeRegistry } from '../../types/schema';
import * as t from '@babel/types';
import { generateCode, addJSDocComment, typedParam, createTypedCallExpression } from './babel-ast';
import {
Expand All @@ -33,6 +33,8 @@ import {
ucFirst,
lcFirst,
getGeneratedFileHeader,
getDeletedNodeIdFieldName,
getDeletePayloadTypeName,
} from './utils';

function isAutoGeneratedField(fieldName: string, pkFieldNames: Set<string>): boolean {
Expand All @@ -58,6 +60,8 @@ export interface MutationGeneratorOptions {
hasRelationships?: boolean;
/** All table type names for determining which types to import from types.ts vs schema-types.ts */
tableTypeNames?: Set<string>;
/** TypeRegistry for looking up actual payload field names from schema introspection */
typeRegistry?: TypeRegistry;
}

export function generateCreateMutationHook(
Expand Down Expand Up @@ -688,6 +692,7 @@ export function generateDeleteMutationHook(
reactQueryEnabled = true,
useCentralizedKeys = true,
hasRelationships = false,
typeRegistry,
} = options;

if (!reactQueryEnabled) {
Expand All @@ -708,7 +713,15 @@ export function generateDeleteMutationHook(
const pkFields = getPrimaryKeyInfo(table);
const pkField = pkFields[0];

const mutationAST = buildDeleteMutationAST({ table });
// Get the correct deletedNodeId field name from schema or convention
const deletePayloadTypeName = getDeletePayloadTypeName(table);
const deletedNodeIdFieldName = getDeletedNodeIdFieldName(
typeRegistry,
deletePayloadTypeName,
typeName
);

const mutationAST = buildDeleteMutationAST({ table, typeRegistry });
const mutationDocument = printGraphQL(mutationAST);

const statements: t.Statement[] = [];
Expand Down Expand Up @@ -792,9 +805,10 @@ export function generateDeleteMutationHook(
);
statements.push(t.exportNamedDeclaration(variablesInterface));

const deletedPkProp = t.tsPropertySignature(
t.identifier(`deleted${ucFirst(pkField.name)}`),
t.tsTypeAnnotation(t.tsUnionType([pkTypeAnnotation, t.tsNullKeyword()]))
// Use the schema-inferred field name for the deleted node ID
const deletedNodeIdProp = t.tsPropertySignature(
t.identifier(deletedNodeIdFieldName),
t.tsTypeAnnotation(t.tsUnionType([t.tsStringKeyword(), t.tsNullKeyword()]))
);
const clientMutationIdProp = t.tsPropertySignature(
t.identifier('clientMutationId'),
Expand All @@ -804,7 +818,7 @@ export function generateDeleteMutationHook(
const resultInterfaceBody = t.tsInterfaceBody([
t.tsPropertySignature(
t.identifier(mutationName),
t.tsTypeAnnotation(t.tsTypeLiteral([clientMutationIdProp, deletedPkProp]))
t.tsTypeAnnotation(t.tsTypeLiteral([clientMutationIdProp, deletedNodeIdProp]))
),
]);
const resultInterface = t.tsInterfaceDeclaration(
Expand Down
46 changes: 46 additions & 0 deletions graphql/codegen/src/cli/codegen/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
CleanTable,
CleanField,
CleanFieldType,
TypeRegistry,
} from '../../types/schema';
import { scalarToTsType, scalarToFilterType } from './scalars';
import { pluralize } from 'inflekt';
Expand Down Expand Up @@ -412,6 +413,51 @@ export function getQueryKeyPrefix(table: CleanTable): string {
return lcFirst(table.name);
}

// ============================================================================
// Delete mutation helpers
// ============================================================================

/**
* Get the deleted node ID field name from a delete mutation payload.
*
* PostGraphile generates delete payloads with a field following the pattern
* `deleted{EntityName}NodeId`. This function looks up the actual field name
* from the TypeRegistry if available, or falls back to the convention.
*
* @param typeRegistry - The type registry from schema introspection
* @param deletePayloadTypeName - The payload type name (e.g., "DeleteUserPayload")
* @param entityTypeName - The entity type name (e.g., "User") for fallback
* @returns The field name (e.g., "deletedUserNodeId")
*/
export function getDeletedNodeIdFieldName(
typeRegistry: TypeRegistry | undefined,
deletePayloadTypeName: string,
entityTypeName: string
): string {
if (typeRegistry) {
const payloadType = typeRegistry.get(deletePayloadTypeName);
if (payloadType?.fields) {
// Find the field that matches the pattern deleted*NodeId
const deletedNodeIdField = payloadType.fields.find(
(f) => f.name.startsWith('deleted') && f.name.endsWith('NodeId')
);
if (deletedNodeIdField) {
return deletedNodeIdField.name;
}
}
}
// Fallback to PostGraphile naming convention
return `deleted${entityTypeName}NodeId`;
}

/**
* Get the delete payload type name for a table.
* Uses inflection if available, otherwise falls back to convention.
*/
export function getDeletePayloadTypeName(table: CleanTable): string {
return table.inflection?.deletePayloadType || `Delete${table.name}Payload`;
}

// ============================================================================
// Code generation helpers
// ============================================================================
Expand Down