slaypni / type-graphql-dataloader Goto Github PK
View Code? Open in Web Editor NEWTypeGraphQL + DataLoader + TypeORM made easy
Home Page: https://www.npmjs.com/package/type-graphql-dataloader
License: MIT License
TypeGraphQL + DataLoader + TypeORM made easy
Home Page: https://www.npmjs.com/package/type-graphql-dataloader
License: MIT License
Is it possible to make @TypeormLoader
fetch eager relations automatically?
By default it doesn't and I have to write the data loader manually.
Today, I used this library when optimizing an n+1 problem of graphql. After adding the corresponding code according to the document, I got an 'data.context._tgdContext' as it is undefined
error. It took me almost an afternoon to troubleshoot the problem. Finally found that this problem is caused by the version of apollo-server-express
. I originally wanted to submit a pr to update the readme, but it seems that I donβt have the relevant permissions, so I can only raise an issue. Hope you can record the corresponding problem in the document for subsequent developers. At present, only apollo-server-express
version 3 can make the latest version of type-graphql-dataloader
run, but I believe there are still many developers who use apollo-server-express
version 2. Using version 2 will cause an error, and 'data.context._tgdContext' as it is undefined
is almost of no help in solving this error
"TypeError: Cannot destructure property 'requestId' of 'data.context._tgdContext' as it is undefined.",
" at /var/app/node_modules/type-graphql-dataloader/dist/decorators/Loader.js:14:21",
" at dispatchHandler (/var/app/node_modules/type-graphql/dist/resolvers/helpers.js:82:30)",
" at Object.applyMiddlewares (/var/app/node_modules/type-graphql/dist/resolvers/helpers.js:88:12)",
" at /var/app/node_modules/type-graphql/dist/resolvers/create.js:27:26",
ApolloServerLoaderPlugin
is added when you new Apolloserver instanceapollo-server-express
, if you use version2 then please use a lower version of type-graphql-dataloader
, such as 0.3.2.Im trying to load a list of workout-sessions and then load them into the dataloader.
The dataloader is being used in a fieldresolver. The goal is to check if any of the workout-sessions, exist in another collection and then return either true or false.
For testing, i have just tried to return a list with all true...
The fieldresolver:
@FieldResolver(returns => Boolean)
@Loader<string, Boolean>(async ids => {
const userSelections = await Userselections.find({ _id: { $in: ids } });
return [true];
})
is_favorite(@Root() root: WorkoutSession) {
return (dataloader: DataLoader<string, Boolean>) =>
dataloader.load(root._id.toString());
}
I get this error:
Does anyone know a solution?
I am using TypeScript, Express and MongoDB
I'm not sure if I'm just missing something, but is there support for passing Ctx
objects to the batch load function? All of my database access is done via a Ctx
option that is passed to my FieldResolver
, but I can't figure out how to give that to my batch function.
Some sample code:
@FieldResolver(() => Group)
@Loader<Member, Group>(
async (member) => {
// I need the dbGateway here to run my queries
}
)
async group(
@Root() root: Member,
@Ctx('db') dbGateway: DbGateway
) {
return (dataLoader: DataLoader<Member, Group>) => {
return dataLoader.load(root);
};
}
I don't see it used on the code, is it from a past experiment before typeorm?
Hey!
I have a simple question, could this package work with type-graphql union types? Spent some time trying to make it work, but without any success.
Hi, I am trying to use this library without the optional TypeORM support, but building the project fails:
node_modules/type-graphql-dataloader/dist/plugins/apollo-server/ApolloServerLoaderPlugin.d.ts:1:28 - error TS2307: Cannot find module 'typeorm' or its corresponding type declarations.
1 import { Connection } from "typeorm";
~~~~~~~~~
node_modules/type-graphql-dataloader/dist/decorators/typeorm/TypeormLoader.d.ts:1:28 - error TS2307: Cannot find module 'typeorm' or its corresponding type declarations.
1 import { ObjectType } from "typeorm";
~~~~~~~~~
Since I'm not using TypeORM, I don't want to add a dependency on it just to provide these types. I've worked around this by creating a typeorm.d.ts
file and manually stubbing those types:
declare module 'typeorm' {
type Connection = any;
type ObjectType<V> = any;
}
Ideally I wouldn't have to do this. This link has some possible solutions to solve this: https://stackoverflow.com/questions/54392809/how-do-i-handle-optional-peer-dependencies-when-publishing-a-typescript-package
Reporting an example where the usage of ResolverInterface
on the resolver break the typings on the property decorated with @FieldResolver.
@Resolver((_of) => Task)
class TaskResolver implements ResolverInterface<Task> {
async category(@Root() task: Task) {
return (dataloader: DataLoader<string, Category>) => {
if (task.categoryId === undefined) return null
return dataloader.load(task.categoryId)
}
}
}
@ObjectType({ description: 'A task is something to do' })
class Task extends TimeStamps {
@GQLField()
_id: string = ''
@MongoField()
@GQLField()
name: string = ''
@MongoField()
@GQLField()
completed: boolean = false
@MongoField()
categoryId?: string
@GQLField({ nullable: true })
category?: Category
}
The error is:
error TS2416: Property 'category' in type 'TaskResolver' is not assignable to the same property in base type 'ResolverInterface<Task>'.
Type '(task: Task) => Promise<(dataloader: DataLoader<string, Category, string>) => Promise<Category> | null>' is not assignable to type '(root: Task, ...args: any[]) => Category | Promise<Category | undefined> | undefined'.
Type 'Promise<(dataloader: DataLoader<string, Category, string>) => Promise<Category> | null>' is not assignable to type 'Category | Promise<Category | undefined> | undefined'.
Type 'Promise<(dataloader: DataLoader<string, Category, string>) => Promise<Category> | null>' is not assignable to type 'Promise<Category | undefined>'.
Property '_id' is missing in type '(dataloader: DataLoader<string, Category>) => Promise<Category> | null' but required in type 'Category'.
46 async category(@Root() task: Task) {
The solution at the moment is not to implement the interface and the implementation works.
It first look ResolverInterface
should be enriched in type-graphql
, but I am posting here first for opinion and feedback and I'll close the issue if it doesn't make sense to be here.
Hey, i am currently encountering an caching issue, caused by the dataloader, which is caching the requests automatically.
This is less of an issue and more of an feature request, to be able to configure caching, either per relation or at start.
I disabled caching by inserting an "clearAll()" after the batch request, but that's not very generic.
From my perspective caching should be configureable somehow
I was investigating what dependencies in my project were needed to update to Apollo-server 4, and as of writing this is the only dependency that needs to be updated as type-graphql 2.0.0 now has a beta release that appears to be stable enough to function with Apollo-server 4, and you already have changes to support TypeORM 0.3.*. I mostly wanted to open this issue to do my due diligence since no other issues mention it.
At the very least if someone else has a solution to Apollo server 4 Plugin creation steps but I am not sure if any breaking changes were made in the beta type-graphql update that would cause type-graphql-dataloader to fail.
I use field arguments to build a complex query inside of the @Loader
batchLoadFn
. With the current code I have to put this arguments as an object parameter of dataLoader.load
like this:
photos(@Root() root: User, @Arg("size", { nullable: true }) size?: string) {
return (dataloader: DataLoader<number, Photo[]>) =>
dataloader.load({
id: root.id,
size: size,
});
}
But arguments could have been extracted directly from the batchLoadFn
, if Loader.ts
had provided all the resolverData
instead of just { context }
import "reflect-metadata";
import { ApolloServer } from "apollo-server-express";
import { ApolloServerLoaderPlugin } from "type-graphql-dataloader";
import * as Express from "express";
import { buildSchema } from "type-graphql";
import { createConnection, getConnection } from "typeorm";
import { UserResolver } from "./resolvers/UserResolver";
import { VideoResolver } from "./resolvers/VideoResolver";
(async () => {
await createConnection().then(() => {
console.log("started");
});
const app = Express();
const apollo = new ApolloServer({
schema: await buildSchema({
resolvers: [UserResolver, VideoResolver]
}),
plugins: [
ApolloServerLoaderPlugin({
typeormGetConnection: getConnection,
}),
],
});
apollo.applyMiddleware({ app });
app.listen(4000, () => {
console.log("server started on http://localhost:4000/graphql");
});
})();
This returns "Cannot read properties of undefined (reading 'typeormGetConnection')" issue when I'm pulling up the relation field. Is Express affecting it?
Currently a relation that is fetched with the @TypeormLoader
decorator won't be returned if the entity that defines the relation is soft deleted. Adding support to fetch these relations even when soft deleted would be much appreciated.
Hi,
With Apollo Server 3, I get this error:
src/api.service.ts:77:17 - error TS2322: Type '{ requestDidStart: () => { didResolveSource(requestContext: { context: Record<string, any>; }): void; willSendResponse(requestContext: { context: Record<string, any>; }): void; }; }' is not assignable to type 'PluginDefinition'.
Type '{ requestDidStart: () => { didResolveSource(requestContext: { context: Record<string, any>; }): void; willSendResponse(requestContext: { context: Record<string, any>; }): void; }; }' is not assignable to type 'ApolloServerPlugin<BaseContext>'.
The types returned by 'requestDidStart(...)' are incompatible between these types.
Type '{ didResolveSource(requestContext: { context: Record<string, any>; }): void; willSendResponse(requestContext: { context: Record<string, any>; }): void; }' is missing the following properties from type 'Promise<void | GraphQLRequestListener<BaseContext>>': then, catch, finally, [Symbol.toStringTag]
77 plugins: [ApolloServerLoaderPlugin()]
Any plans on adding support for Apollo Server 3?
Thanks
Error: ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ':...photos_product.id)' at line 1
TypeORM: v0.2.36
TypeGraphQL: v1.1.1
MariaDB: v10.6.3-1
// Photo Entity
@ObjectType()
@Entity()
export class Photos {
@Field(() => ID)
@PrimaryGeneratedColumn()
id: number;
@Field()
@Column()
path: string;
@Field(() => Product)
@ManyToOne(() => Product, product => product.photos, {
onDelete: "CASCADE",
})
@TypeormLoader()
product: Product;
}
// Product Entity
@ObjectType()
@Entity()
export class Product {
@Field(() => ID)
@PrimaryGeneratedColumn()
id: number;
@Field(() => [Photos])
@OneToMany(() => Photo, photos => photos.product, {
cascade: true,
})
@TypeormLoader()
photos: Photo[];
}
@Resolver()
export class ProductResolver() {
@Query(() => Product)
async product(@Arg("id", () => ID) id: number) {
return getRepository(Product).findOne(id);
}
}
Here is a simple GraphQL query:
query {
product(id: 1) {
photos {
path,
}
}
}
The second query after the product
query that we can see in the log is a query like this:
SELECT
`photos`.`id` AS `photos_id`,
`photos`.`path` AS `photos_path`,
`photos`.`productId` AS `photos_productId`,
`product`.`id` AS `product_id`
FROM `photo` `photos`
INNER JOIN `product` `product` ON `product`.`id`=`photos`.`productId`
WHERE `photos`.`productId` IN (:...photos_product.id)
I tracked the error and I found that in the ImplicitLoaderImpl.ts file, OneToManyDataloader's constructor, we go to the findEntities function and in this line the error happens.
I really don't know that the source of the error is in the structure of typeorm or mariadb, but I tested that by changing this line the error can be resolved.
const keys = columnMetas.map((c) => `${relationName}_${c.propertyPath}`); // keys = ["'photos_product.id'"]
must be changed to
const keys = columnMetas.map((c) => `${relationName}_${c.propertyAliasName}`); // keys = ["photos_product_id"]
When trying to query dataloaded relationships via a GraphQl subscription, you get the following:
"message": "Cannot read property 'typeormGetConnection' of undefined",
I believe it is because the ApolloServerLoaderPlugin is not being passed to the WS server:
async serverWillStart() {
return {
async drainServer() {
subscriptionServer.close();
},
};
},
},
ApolloServerLoaderPlugin({
typeormGetConnection: getConnection,
}),
anyone have an ideas on this?
I am trying to find a way to get entity relations in other internal functions, no just through a resolver.
For example:
Examples:
//User.entity.ts
export class User {
@Field()
name: string
@Field(() => [UserCompanyLink])
@OneToMany(() => UserCompanyLink, UCL=>UCL.user)
@TypeormLoader()
companyProfiles: UserCompanyLink[]
}
//UserCompanyLink.entity.ts
export class UserCompanyLink {
@Field()
company_title: string
@Field()
company_email: string
@Field(() => User)
@ManyToOne(() => User, U => U.companyProfiles)
@TypeormLoader()
user: User
}
//EmailService.ts
export class EmailService {
//...
async sendEmail(UCL: UserCompanyLink){
const user = UCL.user // <- this is undefined as typeorm hasn't loaded anything
//alternative if UCL.user is Promise<User>
const user = await UCL.user // <- this works as intended here, but now gql resolvers don't use the dataloader
// Use the user.name in the email body, but use the company_email from UCL
}
}
Any advice would be hugely helpful. Thanks!
How do i mention the return of photos function as Promise<Photo[] | undefined> ?
@FieldResolver()
@loader<number, Photo[]>(async (ids, { context }) => { // batchLoadFn
const photos = await getRepository(Photo).find({
where: { user: { id: In([...ids]) } },
});
const photosById = groupBy(photos, "userId");
return ids.map((id) => photosById[id] ?? []);
})
photos(@root() root: User) {
return (dataloader: DataLoader<number, Photo[]>) =>
dataloader.load(root.id);
}
Recently typeorm updated their package to 0.3 which changed connection methods.
So getConnection doesn't work.
Hi @slaypni Great project btw! thanks for helping me with my issues before.
Do you think I should have any problem integrating this into my current project?
I'm gonna try to do so following the README.md instructions!
Will keep this as a doc of my own if that's ok or to ask further questions.
Anything you might wanna add/share might be helpful too!
This proyect is not active?
I am getting this error typeormGetConnection is undefined
, and I am unsure why. The docs indicate that Apollo isn't required, but I am unsure if this is the because of this error.
If your application uses Apollo Server, pass ApolloServerLoaderPlugin() as a plugin when instantiating the server.
I'm implementing jest for testing, and it tells me errors :
TypeError: Cannot read properties of undefined (reading 'typeormGetConnection')
and
TypeError: Cannot read properties of null (reading '_tgdContext')
The error was appearing only when an entity that containing Typeormloader is being loaded via jest, but it went normally either on server run or loading data from an entitiy without Typeormloader
Any solution? Thanks
when I run yarn to install deps, the build fails due to vfile-message types missing?
Try npm install @types/vfile-message
if it exists or add a new declaration (.d.ts) file containing declare module 'vfile-message';
11 import * as vfileMessage from 'vfile-message';
~~~~~~~~~~~~~~~
Maybe my node env is broken or something
β y
[!!]y
yarn install v1.22.4
[1/4] π Resolving packages...
success Already up-to-date.
$ npm run-script clean && npm run-script build
> [email protected] clean /Users/agustif/Code/type-graphql-dataloader
> rimraf dist
> [email protected] build /Users/agustif/Code/type-graphql-dataloader
> tsc -p tsconfig.build.json
../../node_modules/@types/vfile/index.d.ts:11:31 - error TS7016: Could not find a declaration file for module 'vfile-message'. '/Users/agustif/node_modules/vfile-message/index.js' implicitly has an 'any' type.
Try `npm install @types/vfile-message` if it exists or add a new declaration (.d.ts) file containing `declare module 'vfile-message';`
11 import * as vfileMessage from 'vfile-message';
~~~~~~~~~~~~~~~
Found 1 error.
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] build: `tsc -p tsconfig.build.json`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] build script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /Users/agustif/.npm/_logs/2020-08-08T13_08_29_228Z-debug.log
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
getConnection
is now deprecated in Typeorm
So the README getting started instructions should do something like this instead:
export const initializeDataSource = async () => {
const options = {...};
return new DataSource(options);
}
const initializeApolloServer = async() => {
const dataSource = await initializeDataSource();
if (dataSource.isInitialized === false) {
logger.debug("Initializing data source...");
await dataSource.initialize();
}
return new ApolloServer({
schema,
plugins: [ ApolloServerLoaderPlugin({ typeormGetConnection: dataSource }),
});
}
You can reproduce it by installing the latest version of apollo-server-express
and typeorm
.
const apolloServer = new ApolloServer({
schema,
context: ({ req, res }: any) => ({ req, res, conn }),
plugins: [
ApolloServerLoaderPlugin({
typeormGetConnection: getConnection
})
]
});
Brief example:
@Entity()
@ObjectType()
export default class User {
@Field()
id!: string;
@Field(() => [Post])
@TypeormLoader(() => Post, (post: Post) => post.userId, {
selfKey: true,
})
@OneToMany(() => Post, (post) => post.user)
posts?: Post[];
}
@Resolver(() => User)
export default class UserResolver {
@FieldResolver(() => Number)
async numPosts(@Root() user: User): Promise<number> {
// currently using:
await user.reload({ relations: ["posts"] });
return user.posts!.length;
// would like to somehow use the user posts resolver to batch, something like:
return (await User.dataloader.load(user.id)).length;
}
}
Is this possible? I understand the example is a bit contrived. I know I can use a custom @Loader
in my numPosts
FieldResolver, but it'd be great if I can take advantage of the existing DataLoader from the model declaration.
thanks!
Edit: I should add, I'm so stoked you wrote this short but powerful library. It's made my life so much easier.
I have 2 tables with many to many relation and in a @jointable decorator I have a name attribute. The problem is that the query uses the default jointable name(not name in decorator).
Hi, I am new to type-graphql-dataloader and I am trying to build a POC to evaluate using it with Apollo and MongoDB.
As you might now MongoDB uses identity keys named _id
, while Dataloader expects identities stored in properties named id
.
Is there any way to change this setting on the Dataloader, so to expect identities stored in properties called _id
?
I imagine that there are other approaches for solving this problem, but if such a setting exist it would seem clean to me.
Thank you for your work!
Hi,
this is more a question rather than an issue.
I'm trying to check if the dataloader is used when calling the fieldresolver. As far as I see, it seems not, but I'm wondering why...
According to the examples you have provided in your repo, looking at the "Company" entity, there is only one FieldResolver. The others column are not "FieldResolved" because there is a @TypeOrmLoader decorator.
To make it clear, does it mean that a FieldResolver isn't required when the @TypeOrmLoader is set for the field?
This part is not that clear.
Thks
Rgds
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.