[
  {
    "path": ".github/workflows/nodejs.yml",
    "content": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions\n\nname: Node.js CI\n\non:\n  push:\n  pull_request:\n    branches: [master]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [14.x, 16.x]\n\n    steps:\n      - uses: actions/checkout@v2\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v1\n        with:\n          node-version: ${{ matrix.node-version }}\n      - run: yarn install --frozen-lockfile\n      - run: yarn tsc -p . --noEmit\n      - run: yarn test\n        env:\n          CI: true\n      - name: Coveralls\n        uses: coverallsapp/github-action@master\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".vscode/\ndist/\n\n### https://raw.github.com/github/gitignore/994f99fc353f523dfe5633067805a1dd4a53040f/Node.gitignore\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.pnp.*\n\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Kris Lefeber\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# NestJS Dataloader\n\n![Node.js CI](https://github.com/krislefeber/nestjs-dataloader/workflows/Node.js%20CI/badge.svg)\n[![Coverage Status](https://coveralls.io/repos/github/krislefeber/nestjs-dataloader/badge.svg?branch=master)](https://coveralls.io/github/krislefeber/nestjs-dataloader?branch=master)\n\nNestJS dataloader simplifies adding [graphql/dataloader](https://github.com/graphql/dataloader) to your NestJS project. DataLoader aims to solve the common N+1 loading problem.\n\n## Installation\n\nInstall with yarn\n\n```bash\nyarn add nestjs-dataloader\n```\n\nInstall with npm\n\n```bash\nnpm install --save nestjs-dataloader\n```\n\n## Usage\n\n### NestDataLoader Creation\n\nWe start by implementing the `NestDataLoader` interface. This tells `DataLoader` how to load our objects.\n\n```typescript\nimport * as DataLoader from 'dataloader';\nimport { Injectable } from '@nestjs/common';\nimport { NestDataLoader } from 'nestjs-dataloader';\n...\n\n@Injectable()\nexport class AccountLoader implements NestDataLoader<string, Account> {\n  constructor(private readonly accountService: AccountService) { }\n\n  generateDataLoader(): DataLoader<string, Account> {\n    return new DataLoader<string, Account>(keys => this.accountService.findByIds(keys));\n  }\n}\n```\n\nThe first generic of the interface is the type of ID the datastore uses. The second generic is the type of object that will be returned. In the above instance, we want `DataLoader` to return instances of the `Account` class.\n\n### Providing the NestDataLoader\n\nFor each NestDataLoader we create, we need to provide it to our module.\n\n```typescript\nimport { Module } from '@nestjs/common';\nimport { APP_INTERCEPTOR } from '@nestjs/core';\nimport {DataLoaderInterceptor} from 'nestjs-dataloader'\n...\n\n@Module({\n  providers: [\n    AccountResolver,\n    AccountLoader,\n    {\n      provide: APP_INTERCEPTOR,\n      useClass: DataLoaderInterceptor,\n    },\n  ],\n\n})\nexport class ResolversModule { }\n```\n\n### Using the NestDataLoader\n\nNow that we have a dataloader and our module is aware of it, we need to pass it as a parameter to an endpoint in our graphQL resolver.\n\n```typescript\nimport * as DataLoader from 'dataloader';\nimport { Loader } from 'nestjs-dataloader';\n...\n\n@Resolver(Account)\nexport class AccountResolver {\n\n    @Query(() => [Account])\n    public getAccounts(\n        @Args({ name: 'ids', type: () => [String] }) ids: string[],\n        @Loader(AccountLoader) accountLoader: DataLoader<Account['id'], Account>): Promise<Account[]> {\n        return accountLoader.loadMany(ids);\n    }\n}\n```\n\nThe important thing to note is that the parameter of the `@Loader` decorator is the entity/class of the `NestDataLoader` we want to be injected to the method. The DataLoader library will handle bulk retrieval and caching of our requests. Note that the caching is stored on a per-request basis.\n\n## Contributing\n\nPull requests are always welcome. For major changes, please open an issue first to discuss what you would like to change.\n"
  },
  {
    "path": "_config.yml",
    "content": "theme: jekyll-theme-slate"
  },
  {
    "path": "example/__tests__/app.e2e-spec.ts",
    "content": "import { Test, TestingModule } from \"@nestjs/testing\";\nimport { INestApplication } from \"@nestjs/common\";\nimport request from 'supertest';\nimport gql from \"graphql-tag\";\nimport { AppModule } from \"./../src/app.module\";\nimport { Factory } from 'typeorm-factory'\nimport { Account } from \"../src/account/account.entity\";\n\ndescribe(\"AppModule\", () => {\n  let app: INestApplication;\n\n  beforeAll(async () => {\n    const moduleFixture: TestingModule = await Test.createTestingModule({\n      imports: [AppModule],\n    }).compile();\n\n    app = moduleFixture.createNestApplication();\n    await app.init();\n  });\n\n  afterAll(() => app.close());\n\n  it(\"defined\", () => expect(app).toBeDefined());\n\n  it(\"/graphql(POST) getAccounts\", async () => {\n    const f = new Factory(Account).attr('name', 'name')\n    const account = await f.create()\n    const query = request(app.getHttpServer()).post;\n    const result = await query('/graphql',{\n      query: gql`\n        query q($ids: [ID!]!) {\n          getAccounts(ids: $ids) {\n            id\n          }\n        }\n      `,\n      variables: {\n        ids: [account.id],\n      },\n    });\n    expect(result.errors).toBeUndefined()\n  });\n});\n"
  },
  {
    "path": "example/src/account/account.entity.ts",
    "content": "import { ObjectType, Field, ID } from \"@nestjs/graphql\";\nimport { Entity, PrimaryGeneratedColumn, Column } from \"typeorm\";\n\n@Entity(\"accounts\")\n@ObjectType()\nexport class Account {\n  @PrimaryGeneratedColumn()\n  @Field(() => ID)\n  readonly id: string;\n\n  @Column()\n  readonly name: string;\n}\n"
  },
  {
    "path": "example/src/account/account.loader.ts",
    "content": "import DataLoader = require(\"dataloader\");\nimport { Injectable } from \"@nestjs/common\";\nimport { NestDataLoader } from \"../../..\";\nimport { AccountService } from \"./account.service\";\nimport { Account } from \"./account.entity\";\n\n@Injectable()\nexport class AccountLoader implements NestDataLoader<string, Account> {\n  constructor(private readonly accountService: AccountService) {}\n\n  generateDataLoader(): DataLoader<string, Account> {\n    return new DataLoader<string, Account>(keys =>\n      this.accountService.findByIds(keys)\n    );\n  }\n}\n"
  },
  {
    "path": "example/src/account/account.module.ts",
    "content": "import { Module } from \"@nestjs/common\";\nimport { AccountResolver } from \"./account.resolver\";\nimport { AccountService } from \"./account.service\";\nimport { APP_INTERCEPTOR } from \"@nestjs/core/constants\";\nimport { DataLoaderInterceptor } from \"../../../index\";\nimport { Account } from \"./account.entity\";\nimport { TypeOrmModule } from \"@nestjs/typeorm\";\nimport { AccountLoader } from \"./account.loader\";\n\n@Module({\n  imports: [TypeOrmModule.forFeature([Account])],\n  providers: [\n    AccountResolver,\n    AccountService,\n    AccountLoader,\n    {\n      provide: APP_INTERCEPTOR,\n      useClass: DataLoaderInterceptor,\n    },\n  ],\n})\nexport class AccountModule {}\n"
  },
  {
    "path": "example/src/account/account.resolver.spec.ts",
    "content": "import { Test, TestingModule } from \"@nestjs/testing\";\nimport { AccountResolver } from \"./account.resolver\";\n\ndescribe(\"AccountResolver\", () => {\n  let resolver: AccountResolver;\n\n  beforeEach(async () => {\n    const module: TestingModule = await Test.createTestingModule({\n      providers: [AccountResolver],\n    }).compile();\n\n    resolver = module.get<AccountResolver>(AccountResolver);\n  });\n\n  it(\"should be defined\", () => expect(resolver).toBeDefined());\n});\n"
  },
  {
    "path": "example/src/account/account.resolver.ts",
    "content": "import { Resolver, Args, Query, ID } from \"@nestjs/graphql\";\nimport * as DataLoader from \"dataloader\";\nimport { Loader } from \"../../../index\";\nimport { Account } from \"./account.entity\";\nimport { AccountLoader } from \"./account.loader\";\n\n@Resolver(\"Account\")\nexport class AccountResolver {\n  @Query(() => [Account])\n  public getAccounts(\n    @Args({ name: \"ids\", type: () => [ID] }) ids: string[],\n    @Loader(AccountLoader)\n    accountLoader: DataLoader<Account[\"id\"], Account>\n  ): Promise<(Account | Error)[]> {\n    return accountLoader.loadMany(ids);\n  }\n}\n"
  },
  {
    "path": "example/src/account/account.service.spec.ts",
    "content": "import { Test, TestingModule } from \"@nestjs/testing\";\nimport { AccountService } from \"./account.service\";\nimport { Account } from \"./account.entity\";\nimport { getRepositoryToken } from \"@nestjs/typeorm\";\n\ndescribe(\"AccountService\", () => {\n  let service: AccountService;\n\n  beforeEach(async () => {\n    const module: TestingModule = await Test.createTestingModule({\n      providers: [\n        AccountService,\n        {\n          provide: getRepositoryToken(Account),\n          useValue: {},\n        },\n      ],\n    }).compile();\n\n    service = module.get<AccountService>(AccountService);\n  });\n\n  it(\"should be defined\", () => {\n    expect(service).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "example/src/account/account.service.ts",
    "content": "import { Injectable } from \"@nestjs/common\";\nimport { InjectRepository } from \"@nestjs/typeorm\";\nimport { Account } from \"./account.entity\";\nimport { Repository, In } from \"typeorm\";\n\n@Injectable()\nexport class AccountService {\n  constructor(\n    @InjectRepository(Account)\n    private readonly accounts: Repository<Account>\n  ) {}\n\n  async findByIds(ids: readonly string[]) {\n    return this.accounts.findByIds(ids as string[]);\n  }\n}\n"
  },
  {
    "path": "example/src/app.module.ts",
    "content": "import { ApolloDriver, ApolloDriverConfig } from \"@nestjs/apollo\";\nimport { Module } from \"@nestjs/common\";\nimport { GraphQLModule } from \"@nestjs/graphql\";\nimport { TypeOrmModule } from \"@nestjs/typeorm\";\nimport { join } from \"path\";\nimport { AccountModule } from \"./account/account.module\";\n\n@Module({\n  imports: [\n    GraphQLModule.forRoot<ApolloDriverConfig>({\n      driver: ApolloDriver,\n      autoSchemaFile: true,\n      debug: true,\n    }),\n    TypeOrmModule.forRoot({\n      type: \"sqlite\",\n      database: \"sample\",\n      entities: [join(__dirname, \"./**/*.entity.[t|j]s\")],\n      synchronize: true,\n    }),\n    AccountModule,\n  ]\n})\nexport class AppModule {}\n"
  },
  {
    "path": "example/src/main.ts",
    "content": "import { NestFactory } from \"@nestjs/core\";\nimport { AppModule } from \"./app.module\";\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule);\n  await app.listen(3000);\n}\nbootstrap();\n"
  },
  {
    "path": "example/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es6\",\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"skipLibCheck\": true,\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"node_modules\", \"*.spec.ts\"]\n}\n"
  },
  {
    "path": "index.ts",
    "content": "import {\n  CallHandler,\n  createParamDecorator,\n  ExecutionContext,\n  Injectable,\n  InternalServerErrorException,\n  NestInterceptor,\n  Type,\n} from '@nestjs/common';\nimport { APP_INTERCEPTOR, ModuleRef, ContextIdFactory } from '@nestjs/core';\nimport { GqlExecutionContext } from '@nestjs/graphql';\nimport * as DataLoader from 'dataloader';\nimport { Observable } from 'rxjs';\n\n/**\n * This interface will be used to generate the initial data loader.                \n * The concrete implementation should be added as a provider to your module.\n */\nexport interface NestDataLoader<ID, Type> {\n  /**\n   * Should return a new instance of dataloader each time\n   */\n  generateDataLoader(): DataLoader<ID, Type>;\n}\n\n/**\n * Context key where get loader function will be stored.\n * This class should be added to your module providers like so:\n * {\n *     provide: APP_INTERCEPTOR,\n *     useClass: DataLoaderInterceptor,\n * },\n */\nconst NEST_LOADER_CONTEXT_KEY: string = \"NEST_LOADER_CONTEXT_KEY\";\n\n@Injectable()\nexport class DataLoaderInterceptor implements NestInterceptor {\n  constructor(private readonly moduleRef: ModuleRef) { }\n  /**\n   * @inheritdoc\n   */\n  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n    const graphqlExecutionContext = GqlExecutionContext.create(context);\n    const ctx = graphqlExecutionContext.getContext();\n\n    if (ctx[NEST_LOADER_CONTEXT_KEY] === undefined) {\n      ctx[NEST_LOADER_CONTEXT_KEY] = {\n        contextId: ContextIdFactory.create(),\n        getLoader: (type: string) : Promise<NestDataLoader<any, any>> => {\n          if (ctx[type] === undefined) {\n            try {\n              ctx[type] = (async () => {\n                return (await this.moduleRef.resolve<NestDataLoader<any, any>>(type, ctx[NEST_LOADER_CONTEXT_KEY].contextId, { strict: false }))\n                  .generateDataLoader();\n              })();\n            } catch (e) {\n              throw new InternalServerErrorException(`The loader ${type} is not provided` + e);\n            }\n          }\n          return ctx[type];\n        }\n      };\n    }\n    return next.handle();\n  }\n}\n\n/**\n * The decorator to be used within your graphql method.\n */\nexport const Loader = createParamDecorator(async (data: Type<NestDataLoader<any, any>>, context: ExecutionContext & { [key: string]: any }) => {\n  const ctx: any = GqlExecutionContext.create(context).getContext();\n  if (ctx[NEST_LOADER_CONTEXT_KEY] === undefined) {\n    throw new InternalServerErrorException(`\n            You should provide interceptor ${DataLoaderInterceptor.name} globally with ${APP_INTERCEPTOR}\n          `);\n  }\n  return await ctx[NEST_LOADER_CONTEXT_KEY].getLoader(data);\n});\n"
  },
  {
    "path": "nest-cli.json",
    "content": "{\n  \"collection\": \"@nestjs/schematics\",\n  \"sourceRoot\": \"example/src\",\n  \"compilerOptions\": {\n    \"plugins\": [\"@nestjs/graphql/plugin\"]\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\r\n  \"name\": \"nestjs-dataloader\",\r\n  \"version\": \"2.0.6\",\r\n  \"description\": \"A NestJS decorator for dataloader\",\r\n  \"license\": \"MIT\",\r\n  \"repository\": \"https://github.com/krislefeber/nestjs-dataloader\",\r\n  \"author\": \"Kris Lefeber <krislefeber@gmail.com>\",\r\n  \"main\": \"dist/index.js\",\r\n  \"scripts\": {\r\n    \"build\": \"tsc -p tsconfig.json\",\r\n    \"prebuild\": \"rm -rf ./dist\",\r\n    \"prepare\": \"tsc -p tsconfig.json\",\r\n    \"prestart\": \"rm -rf ./example/dist\",\r\n    \"start\": \"nest start example/src/main.ts --watch --path=example/tsconfig.json\",\r\n    \"test\": \"jest\"\r\n  },\r\n  \"keywords\": [\r\n    \"nestjs\",\r\n    \"dataloader\",\r\n    \"graphql\"\r\n  ],\r\n  \"dependencies\": {\r\n    \"@nestjs/apollo\": \"^10.0.22\",\r\n    \"dataloader\": \"^2.1.0\",\r\n    \"rxjs\": \"^7.5.6\"\r\n  },\r\n  \"devDependencies\": {\r\n    \"@nestjs/cli\": \"^9.1.1\",\r\n    \"@nestjs/common\": \"^9.0.1\",\r\n    \"@nestjs/core\": \"^9.0.1\",\r\n    \"@nestjs/graphql\": \"^10.0.18\",\r\n    \"@nestjs/platform-express\": \"^9.0.1\",\r\n    \"@nestjs/testing\": \"^9.0.1\",\r\n    \"@nestjs/typeorm\": \"^9.0.1\",\r\n    \"@types/jest\": \"28.1.4\",\r\n    \"apollo-server\": \"3.9.0\",\r\n    \"apollo-server-express\": \"^3.9.0\",\r\n    \"graphql\": \"^16.5.0\",\r\n    \"jest\": \"28.1.3\",\r\n    \"reflect-metadata\": \"^0.1.12\",\r\n    \"sqlite3\": \"^5.0.11\",\r\n    \"supertest\": \"^6.2.4\",\r\n    \"ts-jest\": \"^28.0.8\",\r\n    \"typeorm\": \"0.2.18\",\r\n    \"typeorm-factory\": \"^0.0.14\",\r\n    \"typescript\": \"4.7.4\"\r\n  },\r\n  \"types\": \"index.d.ts\",\r\n  \"jest\": {\r\n    \"transform\": {\r\n      \"^.+\\\\.tsx?$\": \"ts-jest\"\r\n    },\r\n    \"collectCoverage\": true,\r\n    \"globals\": {\r\n      \"ts-jest\": {\r\n        \"diagnostics\": {\r\n          \"warnOnly\": true\r\n        }\r\n      }\r\n    },\r\n    \"testRegex\": \"(/__tests__/.*|(\\\\.|/)(test|spec))\\\\.ts$\",\r\n    \"collectCoverageFrom\": [\r\n      \"index.ts\"\r\n    ]\r\n  },\r\n  \"files\": [\r\n    \"dist\"\r\n  ]\r\n}\r\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": false,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es6\",\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"baseUrl\": \"./\",\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true\n  },\n  \"exclude\": [\"node_modules\", \"dist\", \"example\"]\n}\n"
  }
]