Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ export function buildBelongsToRelations(
sameAttributes(unique.attributes, localAttributes),
);

const remoteCodec = relation.remoteResource?.codec;

belongsTo.push({
fieldName: relationName,
isUnique,
type: relation.remoteResource?.codec?.name || null,
type: remoteCodec?.name || null,
keys: buildFieldList(localAttributes, codec, attributes, context),
references: { name: relation.remoteResource?.codec?.name || 'unknown' },
references: { name: remoteCodec?.name || 'unknown' },
});
}

Expand All @@ -73,12 +75,14 @@ export function buildReverseRelations(
sameAttributes(unique.attributes, remoteAttributes),
);

const remoteCodec = relation.remoteResource?.codec;

const meta: HasRelation = {
fieldName: relationName,
isUnique,
type: relation.remoteResource?.codec?.name || null,
type: remoteCodec?.name || null,
keys: buildFieldList(relation.localAttributes || [], codec, attributes, context),
referencedBy: { name: relation.remoteResource?.codec?.name || 'unknown' },
referencedBy: { name: remoteCodec?.name || 'unknown' },
};

if (isUnique) {
Expand Down
20 changes: 16 additions & 4 deletions graphql/query/src/generators/field-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,11 +293,23 @@ function getRelatedTableScalarFields(
return {};
}

// Find the related table in allTables
const relatedTable = allTables.find((t) => t.name === referencedTableName);
// Find the related table in allTables.
// PostGraphile v5 uses different inflections in different contexts:
// - table.name: PascalCase tableType (e.g., "Shipment", "DriverVehicleAssignment")
// - relation referencedBy.name: raw codec name (e.g., "shipments", "driverVehicleAssignments")
// Try exact match first, then case-insensitive match with optional trailing 's' for plural.
const nameLower = referencedTableName.toLowerCase().replace(/_/g, '');
const nameBase = nameLower.endsWith('s') ? nameLower.slice(0, -1) : nameLower;
const relatedTable =
allTables.find((t) => t.name === referencedTableName) ??
allTables.find((t) => {
const tLower = t.name.toLowerCase().replace(/_/g, '');
return tLower === nameLower || tLower === nameBase;
});
if (!relatedTable) {
// Related table not found in schema - return empty selection
return {};
// Related table not found in schema — return fallback { __typename: true }
// so the query remains valid (nodes need at least one subfield).
return { __typename: true };
}

// Get ALL scalar fields from the related table (non-relational fields)
Expand Down
13 changes: 12 additions & 1 deletion graphql/query/src/generators/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,18 @@ function findRelatedTable(
}

// Find the related table in allTables
return allTables.find((tbl) => tbl.name === referencedTableName) || null;
const exactMatch = allTables.find((tbl) => tbl.name === referencedTableName);
if (exactMatch) return exactMatch;

// Fuzzy match: case-insensitive, strip underscores, optional trailing 's'.
// Needed because relation target names from _meta use snake_case codec names
// (e.g. "routes", "delivery_zone") while allTables[].name is PascalCase (e.g. "Route", "DeliveryZone").
const nameLower = referencedTableName.toLowerCase().replace(/_/g, '');
const nameBase = nameLower.endsWith('s') ? nameLower.slice(0, -1) : nameLower;
return allTables.find((tbl) => {
const tLower = tbl.name.toLowerCase().replace(/_/g, '');
return tLower === nameLower || tLower === nameBase;
}) || null;
}

/**
Expand Down
Loading