Giter Site home page Giter Site logo

slaypni / type-graphql-dataloader Goto Github PK

View Code? Open in Web Editor NEW
150.0 150.0 16.0 396 KB

TypeGraphQL + DataLoader + TypeORM made easy

Home Page: https://www.npmjs.com/package/type-graphql-dataloader

License: MIT License

JavaScript 0.59% TypeScript 99.41%
apollo-server dataloader graphql type-graphql typeorm typescript

type-graphql-dataloader's People

Contributors

giacomorebonato avatar iran-110 avatar slaypni avatar t2y avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

type-graphql-dataloader's Issues

Eager relations with typeorm?

Is it possible to make @TypeormLoader fetch eager relations automatically?

By default it doesn't and I have to write the data loader manually.

Some problems encountered when using the library

describe

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

issues

  1. 'data.context._tgdContext' as it is undefined
"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",
  • please check if ApolloServerLoaderPlugin is added when you new Apolloserver instance
  • check the version of apollo-server-express, if you use version2 then please use a lower version of type-graphql-dataloader, such as 0.3.2.

property 'requestId' of 'context._tgdContext' is undefined

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:

Screenshot 2022-07-15 at 11 30 41

Does anyone know a solution?

I am using TypeScript, Express and MongoDB

Context support?

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);
    };
  }

Support type graphql union types?

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.

TypeORM types required

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

Implementing ResolverInterface breaks typings

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.

Caching

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

Support for Apollo 4

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.

Provide more than just context as a second parameter to batchLoadFn

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 }

Cannot read properties of undefined (reading 'typeormGetConnection')

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?

Support fetching relations when the entity is soft deleted

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.

Support for Apollo Server 3?

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

In the latest version of mariadb and typeorm, using TypeormLoader after OneToMany leads to an error.

Error

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

Versions

TypeORM: v0.2.36
TypeGraphQL: v1.1.1
MariaDB: v10.6.3-1

Entities

// 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

@Resolver()
export class ProductResolver() {
    @Query(() => Product)
    async product(@Arg("id", () => ID) id: number) {
         return getRepository(Product).findOne(id);
    }
}

Query

Here is a simple GraphQL query:

query {
   product(id: 1) {
      photos {
          path,
      }
   }
}

SQL query

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)

Proposal Solution

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"]

Dataloader not working with GraphQL subscriptions

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?

Using TypeormLoader with Lazy fields causes N+1 issue

I am trying to find a way to get entity relations in other internal functions, no just through a resolver.

For example:

  • Two Entities (users and companies) are joined by a third entity (user_company_link)
  • When resolving User -> User_Company_Link -> Company through a gql resolver, everything works exactly as expected. Dataloader is triggered and the result is only 3 queries against the DB
  • When trying to resolve a single User for a specific UCL through an internal request (example below), the User is undefined.
  • Turning the User on a UCL into a Promise works for my internal requests as long as I await the UCL.User, but now the resolver has the N+1 problem again as it looks like it is skipping the dataloader

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!

Could not mention the return type of a field resolver function with @Loader decorator

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);
}

Planning on using type-graphql-dataloader with graphql-modules and accountsjs

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!

Is Apollo required?

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.

Jest shows error when loading relation with type-graphql-dataloader

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

Problem with yarn install/build @types of module 'vfile-message' missing

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 from TypeORM is deprecated -- update README to use DataSource instead

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 }),
    });
}

TS Error with later versions of apollo-server-express and typeorm

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
        })
    ]
});

Is there a way to use a Field TypeormLoader for a computed Field?

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.

ManyToMany jointable name

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).

Can I change id key mapping?

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!

Question more than issue : how check that the dataloader is used ?

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.