mando75 / typeorm-graphql-loader Goto Github PK
View Code? Open in Web Editor NEWA query builder to easily resolve nested fields and relations for TypeORM-based GraphQL servers
Home Page: https://gql-loader.bmuller.net
License: MIT License
A query builder to easily resolve nested fields and relations for TypeORM-based GraphQL servers
Home Page: https://gql-loader.bmuller.net
License: MIT License
Just wanted to say thanks for this great package! Very useful!
Keep up the good work.
I believe where
should use the same type for querying as TypeORM (FindManyOptions<Entity>
or FindOneOptions<Entity>
). Is this correct? If so, I'd be happy to submit a PR updating it.
Currently, this doesn't seem to support nested or inherited fields as defined in the TypeORM Entity
@ObjectType()
@Entity()
export default class Thing1 {
@PrimaryGeneratedColumn()
id: number;
@Field(type => NestedType)
@Column(type => NestedType)
nested: NestedType;
}
@ObjectType()
export default class NestedType {
@Field(type => Date, { nullable: true })
@Column({ nullable: true })
someDate: Date;
}
The query it produces doesn't know that nestedSomeDate
is a column in the DB
helpful for static typing
I'm having troubles getting this library to work.
This is my (simplified) schema:
type Query {
projects: [Project]
}
type Project {
id: ID!
name: String!
activities: [Activity]
}
type Activity {
id: ID!
project: Project!
}
The entities:
@Entity()
export class Activity {
@PrimaryGeneratedColumn("uuid")
id!: string
@ManyToOne(
type => Project,
project => project.activities,
{ eager: true }
)
project!: Project
}
@Entity()
export class Project {
@PrimaryGeneratedColumn("uuid")
id!: string
@Column("varchar", { length: 100, unique: true })
name!: string
@OneToMany(
type => Activity,
activity => activity.project,
{ cascade: true }
)
activities!: Array<Activity>
}
The resolver:
const resolvers: IResolvers = {
Query: {
projects(root: any, args: any, { loader }: Context, info: GraphQLResolveInfo): Promise<Array<Project>> {
return loader.loadMany(Project, {}, info)
},
},
Project: {
async activities(
project: Project,
args: any,
{ loader }: Context,
info: GraphQLResolveInfo
): Promise<Array<Activity>> {
return loader.loadMany<Activity>(Activity, { project }, info)
},
},
}
I was first implementing my resolvers without your library but used standard TypeORM connection and everything worked as expected (but of cause with the N+1 problem) and my test cases were successful .
To solve the N+1 problem I've reworked the resolvers to use the loader.
Problem 1)
This seems to be very fragile. I have a test case that uses this test-query:
{
projects {
id
activities {
id
project {
id
}
}
}
}
When I execute the test 1 out of 5 times it runs successfully but most of the time there are test failures. Sometimes I get GraphQLError: Transaction already started for the given connection, commit current transaction before starting a new one.
and when I log the sql statements I see a ROLLBACK being done. Sometimes it executes the query but some data is missing in the result (some activities are missing from projects).
Problem 2)
It seems to be not solving the N+1 problem. I have another test case that uses this query:
{
projects {
id
activities {
id
}
}
}
The total number of SELECT statements here is numberOfProjects + 1
so for example when I have 100 projects with 10 activities each it executes 101 queries. It's doing 1 SELECT on the projects table and 100 on the activities table.
If I change it to this query:
{
projects {
id
activities {
id
project {
id
activities {
id
}
}
}
}
}
it executes 198 SELECTs.
I assume that my implementation of the resolver is wrong but I'm not sure how I should adjust it.
I also tried to add a resolver function for ' Activity' like this:
const resolvers = {
...
Activity: {
async project(
activity: Activity,
args: any,
{ loader }: Context,
info: GraphQLResolveInfo
): Promise<Project | undefined> {
return loader.loadOne<Project>(Project, { activities: [activity] }, info)
},
},
}
But this wasn't solving the problem but instead now I got a timeout for the last test-case.
Can you give any hints on how to correctly implement the resolvers or where my problem is?
When using LoadManyPaginated, it passes in the queried fields from the root of the query. This works if you are only returning an array of entities, but this means you cannot return pagination metadata.
This appears to work, but only if you don't query for anything more than the primary column, which is fetched by default.
Not really sure what a good solution is here. The LoadManyPaginated function could accept a string as an option that specifies which field to drill into to find the selections for the entity?
I've added a test that reproduces the issue here.
Given an Order and Items relational query, how do I order the items by a field as well as the orders ?
return loader
.loadEntity(Order, "order")
.info(info)
.order({'order.date': 'DESC'})
.loadMany();
@ObjectType()
@Entity()
export class Order extends BaseEntity {
@Field((type) => Int)
@PrimaryGeneratedColumn()
id: number;
@FieldI(type) => String)
@Column()
text: string;
@Field((type) => Date)
@Column()
date: Date;
@Field((type) => [Item], { nullable: true })
@OneToMany((type) => Item, item => item.order)
items Item[];
}
@ObjectType()
@Entity()
export class Item extends BaseEntity {
@Field((type) => Int)
@PrimaryGeneratedColumn()
id: number;
@Field()
@Column()
text: string;
@Field()
@Column()
_number: number;
@Field((type) => Post)
@ManyToOne((type) => Order, (order) => order.items)
@JoinColumn({ name: 'order_id' })
item: Item;
};
I want to be able to add the equivalent to a ```.order({{'item._number': 'ASC'})``` to order items within orders by ```_number```
Hey, thanks for your work on this library, very helpful.
I had a question about field resolvers, how would one avoid the N+1 problem and use this library to handle fields that fetch external data or are calculated based on other fields or even model data?
Hi,
it seems that the problem with TS choosing the wrong overload for
fields.reduce((o: Hash<Selection>, ast: FieldNode)...
in graphqlQueryBuilder.ts can be resolved with
const fields = info.fieldNodes as Array<FieldNode>;
instead of
const fields = info.fieldNodes;
Congrats for the great project - I implemented the same functionality a while ago, but I like your version better.
Hey there, first of all great work with the lib!! it was very easy and fun to consume and implement, and like magic - performance for nested query are sky rocketing.
However, I couldn't find anywhere online an explanation on how to query in the "where" clause foreign-keyed tables. say I have an Item table for which I've written a graphql query, and it has a categoryId linking it to another entity called category (N:1). fetching the category fields works awesome, but when trying to do a search
/ where
with fields like 'category.name', it fails trying to find the table. the reason is that in the SQL only my main entity got a descent alias (item), but the category table got a GUID for an alias or something like that. how can I force a join with my alias? if it was a regular typeorm query builder I would just use innerJoinAndSelect
, but couldn't find such an equivalent in this lib...
I noticed that both GraphQLDatabaseLoader
and GraphQLQueryBuilder
use generic extending typeof BaseEntity
which is incorrect in some cases because Entity
might not be extended from BaseEntity
. It enables some difficulties to implement correct typing in real use. It seems everything works great if entity isn't extended from BaseEntity
, so I think it's redundant typing. Please, fix it.
I am running a few micro-services with multi tenancy (Database isolation) and central GraphQL server, hence TypeOrm connection is created on the fly after checking TenantId sent through GraphQL headers which happen from micro-service.
Is there anyway i can pass the gql args and info to these services and generate query builder from there instead of attaching GraphQLDatabaseLoader with predefined connection to context?
hi
the function ConfigureLoader not working
used it for require select fields
version 1.7.2
also the version 1.7.3 compile with errors (no export for GraphQLDatabaseLoader)
@Field(() => Int) @Column("integer", { nullable: false }) @ConfigureLoader({ required: true }) index: number;
Hi there,
Does this currently support typeorm entities following the Data Mapper approach? (not inheriting from BaseEntity).
Currently I get the following error for all of my entities:
Argument of type 'typeof MyEntity' is not assignable to parameter of type 'typeof BaseEntity'.
Type 'typeof MyEntity' is missing the following properties from type 'typeof BaseEntity': useConnection, getRepository, target, hasId, and 19 more.
named columns are not supported by the package. so for example this entity:
@ObjectType()
@Entity()
export class Author extends BaseEntity {
...
@Field()
@Column("varchar", { name: "mobile" })
phone!: string;
...
}
the column "phone" has its original name as "mobile" in the database.
right now when you query against this model all the values for the other columns are returned but not for this column.
Possible Solution:
I was able to fix this by adding/changing some lines in the GraphQLQueryResolver.ts file.
specifically on line 86 - 95.
fields.forEach(field => {
// Make sure we account for embedded types
const propertyName: string = field.propertyName;
const databaseName: string = field.databaseName;
queryBuilder = queryBuilder.addSelect(
this._formatter.columnSelection(alias, propertyName),
this._formatter.aliasField(alias, databaseName)
);
});
I used the field.databaseName as the alias to access the value for each field.
I will try add a PR, as soon as I was able get the tests ready.
const user = await loader
.loadEntity(User, 'user')
.info(info)
.order({ createdAt: 'DESC' })
.loadMany();
@ObjectType()
@Entity()
export class User extends BaseEntity {
@Field(type => ID)
@PrimaryGeneratedColumn('uuid')
id: string;
................
................
@Field(type => Date)
@CreateDateColumn()
createdAt: Date;
@Field(type => Date)
@UpdateDateColumn()
updatedAt: Date;
}
"errors": [
{
"message": "column \"createdat\" does not exist",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"me"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"message": "column \"createdat\" does not exist",
"name": "QueryFailedError",
"length": 111,
"severity": "ERROR",
"code": "42703",
"position": "6222",
"file": "parse_relation.c",
"line": "3359",
"routine": "errorMissingColumn",
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.