nestjs-query
nestjs-query copied to clipboard
Composite primary keys generating an incorrect schema
Describe the bug
I'm using an entity with composite primary keys. Those primary keys are not used in the generated schema for the read-one, update-one, and delete-one queries/mutations.
Have you read the Contributing Guidelines?
Yes.
To Reproduce
Follow the installation for the example: https://tripss.github.io/nestjs-query/docs/introduction/example
Use this entity:
import { ID, ObjectType } from '@nestjs/graphql';
import { FilterableField, IDField } from '@ptc-org/nestjs-query-graphql';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
@ObjectType('TodoItem')
export class TodoItemEntity {
@IDField(() => ID)
@PrimaryGeneratedColumn()
firstId!: string;
@IDField(() => ID)
@PrimaryGeneratedColumn()
secondId!: string;
@FilterableField()
@Column()
title!: string;
}
And this module:
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { Module } from '@nestjs/common';
import { TodoItemEntity } from './todo-item.entity';
@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
// import the NestjsQueryTypeOrmModule to register the entity with typeorm
// and provide a QueryService
imports: [NestjsQueryTypeOrmModule.forFeature([TodoItemEntity])],
resolvers: [
{
EntityClass: TodoItemEntity,
DTOClass: TodoItemEntity,
},
],
}),
],
})
export class TodoItemModule {}
Expected behavior
The schema generated is using id: ID! for the read-one, update-one, and delete-one queries/mutations.
Generated schema
type TodoItem {
firstId: ID!
secondId: ID!
title: String!
}
type DeleteManyResponse {
# The number of records deleted.
deletedCount: Int!
}
type TodoItemDeleteResponse {
firstId: ID
secondId: ID
title: String
}
type UpdateManyResponse {
# The number of records updated.
updatedCount: Int!
}
type TodoItemEdge {
# The node containing the TodoItem
node: TodoItem!
# Cursor for this node.
cursor: ConnectionCursor!
}
# Cursor for paging through collections
scalar ConnectionCursor
type PageInfo {
# true if paging forward and there are more records.
hasNextPage: Boolean
# true if paging backwards and there are more records.
hasPreviousPage: Boolean
# The cursor of the first returned record.
startCursor: ConnectionCursor
# The cursor of the last returned record.
endCursor: ConnectionCursor
}
type TodoItemConnection {
# Paging information
pageInfo: PageInfo!
# Array of edges.
edges: [TodoItemEdge!]!
}
type Query {
todoItem(
# The id of the record to find.
id: ID!
): TodoItem!
todoItems(
# Limit or page results.
paging: CursorPaging! = { first: 10 }
# Specify to filter the records returned.
filter: TodoItemFilter! = {}
# Specify to sort results.
sorting: [TodoItemSort!]! = []
): TodoItemConnection!
}
input CursorPaging {
# Paginate before opaque cursor
before: ConnectionCursor
# Paginate after opaque cursor
after: ConnectionCursor
# Paginate first
first: Int
# Paginate last
last: Int
}
input TodoItemFilter {
and: [TodoItemFilter!]
or: [TodoItemFilter!]
firstId: IDFilterComparison
secondId: IDFilterComparison
title: StringFieldComparison
}
input IDFilterComparison {
is: Boolean
isNot: Boolean
eq: ID
neq: ID
gt: ID
gte: ID
lt: ID
lte: ID
like: ID
notLike: ID
iLike: ID
notILike: ID
in: [ID!]
notIn: [ID!]
}
input StringFieldComparison {
is: Boolean
isNot: Boolean
eq: String
neq: String
gt: String
gte: String
lt: String
lte: String
like: String
notLike: String
iLike: String
notILike: String
in: [String!]
notIn: [String!]
}
input TodoItemSort {
field: TodoItemSortFields!
direction: SortDirection!
nulls: SortNulls
}
enum TodoItemSortFields {
firstId
secondId
title
}
# Sort Directions
enum SortDirection {
ASC
DESC
}
# Sort Nulls Options
enum SortNulls {
NULLS_FIRST
NULLS_LAST
}
type Mutation {
createOneTodoItem(input: CreateOneTodoItemInput!): TodoItem!
createManyTodoItems(input: CreateManyTodoItemsInput!): [TodoItem!]!
updateOneTodoItem(input: UpdateOneTodoItemInput!): TodoItem!
updateManyTodoItems(input: UpdateManyTodoItemsInput!): UpdateManyResponse!
deleteOneTodoItem(input: DeleteOneTodoItemInput!): TodoItemDeleteResponse!
deleteManyTodoItems(input: DeleteManyTodoItemsInput!): DeleteManyResponse!
}
input CreateOneTodoItemInput {
# The record to create
todoItem: CreateTodoItem!
}
input CreateTodoItem {
firstId: ID!
secondId: ID!
title: String!
}
input CreateManyTodoItemsInput {
# Array of records to create
todoItems: [CreateTodoItem!]!
}
input UpdateOneTodoItemInput {
# The id of the record to update
id: ID!
# The update to apply.
update: UpdateTodoItem!
}
input UpdateTodoItem {
firstId: ID
secondId: ID
title: String
}
input UpdateManyTodoItemsInput {
# Filter used to find fields to update
filter: TodoItemUpdateFilter!
# The update to apply to all records found using the filter
update: UpdateTodoItem!
}
input TodoItemUpdateFilter {
and: [TodoItemUpdateFilter!]
or: [TodoItemUpdateFilter!]
firstId: IDFilterComparison
secondId: IDFilterComparison
title: StringFieldComparison
}
input DeleteOneTodoItemInput {
# The id of the record to delete.
id: ID!
}
input DeleteManyTodoItemsInput {
# Filter to find records to delete
filter: TodoItemDeleteFilter!
}
input TodoItemDeleteFilter {
and: [TodoItemDeleteFilter!]
or: [TodoItemDeleteFilter!]
firstId: IDFilterComparison
secondId: IDFilterComparison
title: StringFieldComparison
}
I would expect something like:
type Query {
todoItem(
- # The id of the record to find.
- id: ID!
+ firstId: ID!
+ secondId: ID!
): TodoItem!
}
input UpdateOneTodoItemInput {
- # The id of the record to update
- id: ID!
+ firstId: ID!
+ secondId: ID!
# The update to apply.
update: UpdateTodoItem!
}
input DeleteOneTodoItemInput {
- # The id of the record to delete.
- id: ID!
+ firstId: ID!
+ secondId: ID!
}
Desktop (please complete the following information):
- Node Version: v22.2.0
- @ptc-org/nestjs-query-core: ^6.1.0,
- @ptc-org/nestjs-query-graphql: ^6.1.0,
- @ptc-org/nestjs-query-typeorm: ^6.1.0,
Interesting case, if I look at the code everywhere where we use IDField it only expects one field, to change this will be a challenge as we would then go from one known field (id) to 1 or more unknown fields.
Less then ideal solution: mark the second primary field as a required filter field?