├── .circleci
└── config.yml
├── .commitlintrc.json
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── .release-it.json
├── README.md
├── index.d.ts
├── index.js
├── index.ts
├── lib
├── decorators
│ ├── args-type.decorator.ts
│ ├── args.decorator.ts
│ ├── context.decorator.ts
│ ├── directive.decorator.ts
│ ├── extensions.decorator.ts
│ ├── field.decorator.ts
│ ├── hide-field.decorator.ts
│ ├── index.ts
│ ├── info.decorator.ts
│ ├── input-type.decorator.ts
│ ├── interface-type.decorator.ts
│ ├── mutation.decorator.ts
│ ├── object-type.decorator.ts
│ ├── param.utils.ts
│ ├── parent.decorator.ts
│ ├── query.decorator.ts
│ ├── resolve-field.decorator.ts
│ ├── resolver.decorator.ts
│ ├── resolvers.utils.ts
│ ├── root.decorator.ts
│ ├── scalar.decorator.ts
│ └── subscription.decorator.ts
├── enums
│ ├── class-type.enum.ts
│ ├── gql-paramtype.enum.ts
│ └── resolver.enum.ts
├── factories
│ └── params.factory.ts
├── fgql.constants.ts
├── fgql.module.ts
├── graphql
│ ├── graphql-ast.explorer.ts
│ ├── graphql-schema.builder.ts
│ ├── graphql-schema.host.ts
│ ├── graphql-types.loader.ts
│ ├── graphql.factory.ts
│ └── index.ts
├── index.ts
├── interfaces
│ ├── base-type-options.interface.ts
│ ├── build-schema-options.interface.ts
│ ├── complexity.interface.ts
│ ├── custom-scalar.interface.ts
│ ├── fgql-module-options.interface.ts
│ ├── gql-exception-filter.interface.ts
│ ├── index.ts
│ ├── resolve-type-fn.interface.ts
│ ├── resolver-metadata.interface.ts
│ ├── return-type-func.interface.ts
│ └── type-options.interface.ts
├── plugin
│ ├── compiler-plugin.ts
│ ├── index.ts
│ ├── merge-options.ts
│ ├── plugin-constants.ts
│ ├── utils
│ │ ├── ast-utils.ts
│ │ └── plugin-utils.ts
│ └── visitors
│ │ └── model-class.visitor.ts
├── scalars
│ ├── index.ts
│ ├── iso-date.scalar.ts
│ └── timestamp.scalar.ts
├── schema-builder
│ ├── errors
│ │ ├── cannot-determine-input-type.error.ts
│ │ ├── cannot-determine-output-type.error.ts
│ │ ├── default-nullable-conflict.error.ts
│ │ ├── default-values-conflict.error.ts
│ │ ├── directive-parsing.error.ts
│ │ ├── invalid-nullable-option.error.ts
│ │ ├── return-type-cannot-be-resolved.error.ts
│ │ ├── schema-generation.error.ts
│ │ ├── unable-to-find-fields.error.ts
│ │ ├── undefined-resolver-type.error.ts
│ │ ├── undefined-return-type.error.ts
│ │ └── undefined-type.error.ts
│ ├── factories
│ │ ├── args.factory.ts
│ │ ├── ast-definition-node.factory.ts
│ │ ├── enum-definition.factory.ts
│ │ ├── factories.ts
│ │ ├── input-type-definition.factory.ts
│ │ ├── input-type.factory.ts
│ │ ├── interface-definition.factory.ts
│ │ ├── mutation-type.factory.ts
│ │ ├── object-type-definition.factory.ts
│ │ ├── orphaned-types.factory.ts
│ │ ├── output-type.factory.ts
│ │ ├── query-type.factory.ts
│ │ ├── resolve-type.factory.ts
│ │ ├── root-type.factory.ts
│ │ ├── subscription-type.factory.ts
│ │ └── union-definition.factory.ts
│ ├── graphql-schema.factory.ts
│ ├── helpers
│ │ ├── file-system.helper.ts
│ │ └── get-default-value.helper.ts
│ ├── index.ts
│ ├── metadata
│ │ ├── class.metadata.ts
│ │ ├── directive.metadata.ts
│ │ ├── enum.metadata.ts
│ │ ├── extensions.metadata.ts
│ │ ├── index.ts
│ │ ├── interface.metadata.ts
│ │ ├── object-type.metadata.ts
│ │ ├── param.metadata.ts
│ │ ├── property.metadata.ts
│ │ ├── resolver.metadata.ts
│ │ └── union.metadata.ts
│ ├── schema-builder.module.ts
│ ├── services
│ │ ├── orphaned-reference.registry.ts
│ │ ├── type-fields.accessor.ts
│ │ └── type-mapper.service.ts
│ ├── storages
│ │ ├── index.ts
│ │ ├── lazy-metadata.storage.ts
│ │ ├── type-definitions.storage.ts
│ │ └── type-metadata.storage.ts
│ ├── type-definitions.generator.ts
│ └── utils
│ │ ├── get-fields-and-decorator.util.ts
│ │ ├── is-target-equal-util.ts
│ │ └── is-throwing.util.ts
├── services
│ ├── base-explorer.service.ts
│ ├── gql-arguments-host.ts
│ ├── gql-execution-context.ts
│ ├── index.ts
│ ├── resolvers-explorer.service.ts
│ └── scalars-explorer.service.ts
├── type-factories
│ ├── create-union-type.factory.ts
│ ├── index.ts
│ └── register-enum-type.factory.ts
└── utils
│ ├── add-class-type-metadata.util.ts
│ ├── async-iterator.util.ts
│ ├── extend.util.ts
│ ├── extract-metadata.util.ts
│ ├── generate-token.util.ts
│ ├── index.ts
│ ├── is-pipe.util.ts
│ ├── merge-defaults.util.ts
│ ├── normalize-route-path.util.ts
│ ├── reflection.utilts.ts
│ ├── remove-temp.util.ts
│ └── scalar-types.utils.ts
├── package.json
├── plugin.js
├── plugin.ts
├── tests
├── code-first
│ ├── app.module.ts
│ ├── common
│ │ ├── filters
│ │ │ └── unauthorized.filter.ts
│ │ ├── guards
│ │ │ └── auth.guard.ts
│ │ └── scalars
│ │ │ └── date.scalar.ts
│ ├── directions
│ │ ├── directions.module.ts
│ │ └── directions.resolver.ts
│ ├── enums
│ │ └── direction.enum.ts
│ ├── main.ts
│ ├── other
│ │ ├── abstract.resolver.ts
│ │ └── sample-orphaned.type.ts
│ └── recipes
│ │ ├── dto
│ │ ├── filter-recipes-count.args.ts
│ │ ├── new-recipe.input.ts
│ │ └── recipes.args.ts
│ │ ├── models
│ │ ├── category.ts
│ │ ├── ingredient.ts
│ │ └── recipe.ts
│ │ ├── recipes.module.ts
│ │ ├── recipes.resolver.ts
│ │ ├── recipes.service.ts
│ │ └── unions
│ │ └── search-result.union.ts
├── e2e
│ ├── code-first-schema.spec.ts
│ └── code-first.spec.ts
└── utils
│ ├── introspection-schema.utils.ts
│ └── printed-schema.snapshot.ts
├── tsconfig.json
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | aliases:
4 | - &restore-cache
5 | restore_cache:
6 | key: dependency-cache-{{ checksum "package.json" }}
7 | - &install-deps
8 | run:
9 | name: Install dependencies
10 | command: yarn --frozen-lockfile
11 | - &build-packages
12 | run:
13 | name: Build
14 | command: yarn build
15 |
16 | jobs:
17 | build:
18 | working_directory: ~/nest
19 | docker:
20 | - image: circleci/node:12
21 | steps:
22 | - checkout
23 | - restore_cache:
24 | key: dependency-cache-{{ checksum "package.json" }}
25 | - run:
26 | name: Install dependencies
27 | command: yarn --frozen-lockfile
28 | - save_cache:
29 | key: dependency-cache-{{ checksum "package.json" }}
30 | paths:
31 | - ./node_modules
32 | - run:
33 | name: Build
34 | command: yarn build
35 |
36 | integration_tests:
37 | working_directory: ~/nest
38 | docker:
39 | - image: circleci/node:12
40 | steps:
41 | - checkout
42 | - *restore-cache
43 | - *install-deps
44 | - run:
45 | name: Integration tests
46 | command: yarn test:integration
47 |
48 | workflows:
49 | version: 2
50 | build-and-test:
51 | jobs:
52 | - build
53 | - integration_tests:
54 | requires:
55 | - build
56 |
--------------------------------------------------------------------------------
/.commitlintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@commitlint/config-angular"],
3 | "rules": {
4 | "subject-case": [
5 | 2,
6 | "always",
7 | ["sentence-case", "start-case", "pascal-case", "upper-case", "lower-case"]
8 | ],
9 | "type-enum": [
10 | 2,
11 | "always",
12 | [
13 | "build",
14 | "chore",
15 | "ci",
16 | "docs",
17 | "feat",
18 | "fix",
19 | "perf",
20 | "refactor",
21 | "revert",
22 | "style",
23 | "test",
24 | "sample"
25 | ]
26 | ]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | tests/**
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | sourceType: 'module',
6 | },
7 | plugins: ['@typescript-eslint/eslint-plugin'],
8 | extends: [
9 | 'plugin:@typescript-eslint/eslint-recommended',
10 | 'plugin:@typescript-eslint/recommended',
11 | 'prettier',
12 | 'prettier/@typescript-eslint',
13 | ],
14 | root: true,
15 | env: {
16 | node: true,
17 | jest: true,
18 | },
19 | rules: {
20 | '@typescript-eslint/interface-name-prefix': 'off',
21 | '@typescript-eslint/explicit-function-return-type': 'off',
22 | '@typescript-eslint/no-explicit-any': 'off',
23 | '@typescript-eslint/no-use-before-define': 'off',
24 | '@typescript-eslint/no-unused-vars': 'off',
25 | '@typescript-eslint/explicit-module-boundary-types': 'off',
26 | '@typescript-eslint/ban-types': 'off',
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 |
4 | # IDE
5 | /.idea
6 | /.awcache
7 | /.vscode
8 |
9 | # misc
10 | npm-debug.log
11 | yarn-error.log
12 | package-lock.json
13 | .DS_Store
14 |
15 | # tests
16 | /test
17 | /coverage
18 | /.nyc_output
19 | test-schema.graphql
20 | *.test-definitions.ts
21 |
22 | # dist
23 | /lib/src
24 | /dist
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # source
2 | lib
3 | tests
4 | index.ts
5 | package-lock.json
6 | tsconfig.json
7 | .prettierrc
8 |
9 | # misc
10 | .commitlintrc.json
11 | .release-it.json
12 | .eslintignore
13 | .eslintrc.js
14 | renovate.json
15 | .prettierignore
16 | .prettierrc
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | tests/generated-definitions/*.ts
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "singleQuote": true
4 | }
5 |
--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 | "git": {
3 | "commitMessage": "chore(): release v${version}"
4 | },
5 | "github": {
6 | "release": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | nest-fgql
2 | =========
3 |
4 | A fast and lightweight module to expose [GraphQL](https://graphql.org/) APIs in a [NestJS](https://nestjs.com) application.
5 |
6 |
7 |
8 |
9 |
10 |
11 | ## Description
12 |
13 | This is intended to be a drop-in replacement for [@nestjs/graphql](https://github.com/nestjs/graphql) that offers improved runtime performance.
14 |
15 | [Benchmarks](https://github.com/benawad/node-graphql-benchmarks) show that Apollo adds a noteable overhead regarding performance. Using [fastify](https://github.com/fastify/fastify) and [graphql-jit](https://github.com/zalando-incubator/graphql-jit) via [fastify-gql](https://github.com/mcollina/fastify-gql) performance is improved.
16 |
17 | ## Usage
18 |
19 | ### Requirements
20 |
21 | Must first follow the [Performance (Fastify)
22 | ](https://docs.nestjs.com/techniques/performance) guide to setup [fastify](https://github.com/fastify/fastify).
23 |
24 | ### Install
25 |
26 | ```
27 | yarn add @mirco312312/nest-fgql
28 | ```
29 |
30 | ### Code
31 |
32 | ```
33 | import { Module } from '@nestjs/common';
34 | import { FgqlModule } from '@mirco312312/nest-fgql';
35 | // ... your other imports
36 |
37 | @Module({
38 | imports: [
39 | // ... preceeding modules
40 | FgqlModule.forRoot({
41 | autoSchemaFile: true,
42 | // ... any other options
43 | }),
44 | // ... more modules
45 | ],
46 | })
47 | export class ApplicationModule {}
48 | ```
49 |
50 | ## Performance
51 |
52 | Start the test scenario for an environment.
53 |
54 | ### @nestjs/graphql
55 |
56 | ```
57 | git clone https://github.com/nestjs/graphql
58 | cd graphql
59 | npm i
60 | npx ts-node tests/code-first/main.ts
61 | ```
62 |
63 | ### nest-fgql
64 |
65 | ```
66 | git clone https://github.com/mirco312312/nest-fgql
67 | cd nest-fgql
68 | yarn
69 | npx ts-node tests/code-first/main.ts
70 | ```
71 |
72 | ### Execute [autocannon](https://github.com/mcollina/autocannon)
73 |
74 | ```
75 | autocannon -d 10 -c100 \
76 | -m POST \
77 | -H 'Content-Type: application/json' \
78 | -b '{"operationName":null,"variables":{},"query":"{\n categories {\n name\n description\n tags\n }\n recipes {\n id\n ingredients {\n name\n }\n rating\n averageRating\n }\n}\n"}' \
79 | http://localhost:3000/graphql
80 | ```
81 |
82 | ### Results
83 |
84 | MacBook Pro (16-inch, 2019)
85 |
86 | - 2,4 GHz 8-Core Intel Core i9
87 | - 64 GB 2667 MHz DDR4
88 |
89 | #### @nestjs/graphql
90 |
91 | ```
92 | Running 10s test @ http://localhost:3000/graphql
93 | 100 connections
94 |
95 | ┌─────────┬───────┬───────┬───────┬───────┬──────────┬─────────┬───────────┐
96 | │ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
97 | ├─────────┼───────┼───────┼───────┼───────┼──────────┼─────────┼───────────┤
98 | │ Latency │ 25 ms │ 29 ms │ 61 ms │ 67 ms │ 30.89 ms │ 8.83 ms │ 176.23 ms │
99 | └─────────┴───────┴───────┴───────┴───────┴──────────┴─────────┴───────────┘
100 | ┌───────────┬────────┬────────┬─────────┬─────────┬─────────┬────────┬────────┐
101 | │ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
102 | ├───────────┼────────┼────────┼─────────┼─────────┼─────────┼────────┼────────┤
103 | │ Req/Sec │ 1613 │ 1613 │ 3429 │ 3579 │ 3183.2 │ 576.32 │ 1613 │
104 | ├───────────┼────────┼────────┼─────────┼─────────┼─────────┼────────┼────────┤
105 | │ Bytes/Sec │ 807 kB │ 807 kB │ 1.72 MB │ 1.79 MB │ 1.59 MB │ 288 kB │ 807 kB │
106 | └───────────┴────────┴────────┴─────────┴─────────┴─────────┴────────┴────────┘
107 |
108 | Req/Bytes counts sampled once per second.
109 |
110 | 32k requests in 10.05s, 15.9 MB read
111 | ```
112 |
113 | #### nest-fgql
114 |
115 | ```
116 | Running 10s test @ http://localhost:3000/graphql
117 | 100 connections
118 |
119 | ┌─────────┬──────┬──────┬───────┬───────┬─────────┬─────────┬──────────┐
120 | │ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
121 | ├─────────┼──────┼──────┼───────┼───────┼─────────┼─────────┼──────────┤
122 | │ Latency │ 7 ms │ 8 ms │ 12 ms │ 13 ms │ 8.94 ms │ 1.52 ms │ 29.08 ms │
123 | └─────────┴──────┴──────┴───────┴───────┴─────────┴─────────┴──────────┘
124 | ┌───────────┬─────────┬─────────┬─────────┬────────┬─────────┬─────────┬─────────┐
125 | │ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
126 | ├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤
127 | │ Req/Sec │ 10327 │ 10327 │ 10591 │ 10935 │ 10607.2 │ 164.39 │ 10326 │
128 | ├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤
129 | │ Bytes/Sec │ 4.15 MB │ 4.15 MB │ 4.26 MB │ 4.4 MB │ 4.26 MB │ 66.2 kB │ 4.15 MB │
130 | └───────────┴─────────┴─────────┴─────────┴────────┴─────────┴─────────┴─────────┘
131 |
132 | Req/Bytes counts sampled once per second.
133 |
134 | 106k requests in 10.05s, 42.6 MB read
135 | ```
136 |
137 | ## Credits
138 |
139 | Heavily based on [@nestjs/graphql](https://github.com/nestjs/graphql).
140 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from './dist';
2 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | function __export(m) {
3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
4 | }
5 | exports.__esModule = true;
6 | __export(require("./dist"));
7 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dist';
2 |
--------------------------------------------------------------------------------
/lib/decorators/args-type.decorator.ts:
--------------------------------------------------------------------------------
1 | import { ClassType } from '../enums/class-type.enum';
2 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
3 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
4 | import { addClassTypeMetadata } from '../utils/add-class-type-metadata.util';
5 |
6 | /**
7 | * Decorator that marks a class as a resolver arguments type.
8 | */
9 | export function ArgsType(): ClassDecorator {
10 | return (target: Function) => {
11 | const metadata = {
12 | name: target.name,
13 | target,
14 | };
15 | LazyMetadataStorage.store(() =>
16 | TypeMetadataStorage.addArgsMetadata(metadata),
17 | );
18 | addClassTypeMetadata(target, ClassType.ARGS);
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/lib/decorators/args.decorator.ts:
--------------------------------------------------------------------------------
1 | import { PipeTransform, Type } from '@nestjs/common';
2 | import {
3 | isFunction,
4 | isObject,
5 | isString,
6 | } from '@nestjs/common/utils/shared.utils';
7 | import 'reflect-metadata';
8 | import { GqlParamtype } from '../enums/gql-paramtype.enum';
9 | import { BaseTypeOptions } from '../interfaces';
10 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
11 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
12 | import { isPipe } from '../utils/is-pipe.util';
13 | import { reflectTypeFromMetadata } from '../utils/reflection.utilts';
14 | import { addPipesMetadata } from './param.utils';
15 |
16 | /**
17 | * Interface defining options that can be passed to `@Args()` decorator.
18 | */
19 | export interface ArgsOptions extends BaseTypeOptions {
20 | /**
21 | * Name of the argument.
22 | */
23 | name?: string;
24 | /**
25 | * Description of the argument.
26 | */
27 | description?: string;
28 | /**
29 | * Function that returns a reference to the arguments host class.
30 | */
31 | type?: () => any;
32 | }
33 |
34 | /**
35 | * Resolver method parameter decorator. Extracts the arguments
36 | * object from the underlying platform and populates the decorated
37 | * parameter with the value of either all arguments or a single specified argument.
38 | */
39 | export function Args(): ParameterDecorator;
40 | /**
41 | * Resolver method parameter decorator. Extracts the arguments
42 | * object from the underlying platform and populates the decorated
43 | * parameter with the value of either all arguments or a single specified argument.
44 | */
45 | export function Args(
46 | ...pipes: (Type | PipeTransform)[]
47 | ): ParameterDecorator;
48 | /**
49 | * Resolver method parameter decorator. Extracts the arguments
50 | * object from the underlying platform and populates the decorated
51 | * parameter with the value of either all arguments or a single specified argument.
52 | */
53 | export function Args(
54 | property: string,
55 | ...pipes: (Type | PipeTransform)[]
56 | ): ParameterDecorator;
57 | /**
58 | * Resolver method parameter decorator. Extracts the arguments
59 | * object from the underlying platform and populates the decorated
60 | * parameter with the value of either all arguments or a single specified argument.
61 | */
62 | export function Args(
63 | options: ArgsOptions,
64 | ...pipes: (Type | PipeTransform)[]
65 | ): ParameterDecorator;
66 | /**
67 | * Resolver method parameter decorator. Extracts the arguments
68 | * object from the underlying platform and populates the decorated
69 | * parameter with the value of either all arguments or a single specified argument.
70 | */
71 | export function Args(
72 | property: string,
73 | options: ArgsOptions,
74 | ...pipes: (Type | PipeTransform)[]
75 | ): ParameterDecorator;
76 | /**
77 | * Resolver method parameter decorator. Extracts the arguments
78 | * object from the underlying platform and populates the decorated
79 | * parameter with the value of either all arguments or a single specified argument.
80 | */
81 | export function Args(
82 | propertyOrOptionsOrPipe?:
83 | | string
84 | | (Type | PipeTransform)
85 | | ArgsOptions,
86 | optionsOrPipe?: ArgsOptions | (Type | PipeTransform),
87 | ...pipes: (Type | PipeTransform)[]
88 | ): ParameterDecorator {
89 | const [property, argOptions, argPipes] = getArgsOptions(
90 | propertyOrOptionsOrPipe,
91 | optionsOrPipe,
92 | pipes,
93 | );
94 |
95 | return (target: Object, key: string, index: number) => {
96 | addPipesMetadata(GqlParamtype.ARGS, property, argPipes, target, key, index);
97 |
98 | LazyMetadataStorage.store(target.constructor as Type, () => {
99 | const { typeFn: reflectedTypeFn, options } = reflectTypeFromMetadata({
100 | metadataKey: 'design:paramtypes',
101 | prototype: target,
102 | propertyKey: key,
103 | index: index,
104 | explicitTypeFn: argOptions.type,
105 | typeOptions: argOptions,
106 | });
107 |
108 | const metadata = {
109 | target: target.constructor,
110 | methodName: key,
111 | typeFn: reflectedTypeFn,
112 | index,
113 | options,
114 | };
115 |
116 | if (property && isString(property)) {
117 | TypeMetadataStorage.addMethodParamMetadata({
118 | kind: 'arg',
119 | name: property,
120 | description: argOptions.description,
121 | ...metadata,
122 | });
123 | } else {
124 | TypeMetadataStorage.addMethodParamMetadata({
125 | kind: 'args',
126 | ...metadata,
127 | });
128 | }
129 | });
130 | };
131 | }
132 |
133 | function getArgsOptions(
134 | propertyOrOptionsOrPipe?:
135 | | string
136 | | (Type | PipeTransform)
137 | | ArgsOptions,
138 | optionsOrPipe?: ArgsOptions | (Type | PipeTransform),
139 | pipes?: (Type | PipeTransform)[],
140 | ): [string, ArgsOptions, (Type | PipeTransform)[]] {
141 | if (!propertyOrOptionsOrPipe || isString(propertyOrOptionsOrPipe)) {
142 | const propertyKey = propertyOrOptionsOrPipe as string;
143 |
144 | let options = {};
145 | let argPipes = [];
146 | if (isPipe(optionsOrPipe)) {
147 | argPipes = [optionsOrPipe].concat(pipes);
148 | } else {
149 | options = optionsOrPipe || {};
150 | argPipes = pipes;
151 | }
152 | return [propertyKey, options, argPipes];
153 | }
154 |
155 | const isArgsOptionsObject =
156 | isObject(propertyOrOptionsOrPipe) &&
157 | !isFunction((propertyOrOptionsOrPipe as PipeTransform).transform);
158 | if (isArgsOptionsObject) {
159 | const argOptions = propertyOrOptionsOrPipe as ArgsOptions;
160 | const propertyKey = argOptions.name;
161 | const argPipes = optionsOrPipe ? [optionsOrPipe].concat(pipes) : pipes;
162 |
163 | return [
164 | propertyKey,
165 | argOptions,
166 | argPipes as (Type | PipeTransform)[],
167 | ];
168 | }
169 |
170 | // concatenate all pipes
171 | let argPipes = [propertyOrOptionsOrPipe];
172 | if (optionsOrPipe) {
173 | argPipes = argPipes.concat(optionsOrPipe);
174 | }
175 | argPipes = argPipes.concat(pipes);
176 |
177 | return [undefined, {}, argPipes as (Type | PipeTransform)[]];
178 | }
179 |
--------------------------------------------------------------------------------
/lib/decorators/context.decorator.ts:
--------------------------------------------------------------------------------
1 | import { PipeTransform, Type } from '@nestjs/common';
2 | import 'reflect-metadata';
3 | import { GqlParamtype } from '../enums/gql-paramtype.enum';
4 | import { createGqlPipesParamDecorator } from './param.utils';
5 |
6 | /**
7 | * Resolver method parameter decorator. Extracts the `Context`
8 | * object from the underlying platform and populates the decorated
9 | * parameter with the value of `Context`.
10 | */
11 | export function Context(): ParameterDecorator;
12 | /**
13 | * Resolver method parameter decorator. Extracts the `Context`
14 | * object from the underlying platform and populates the decorated
15 | * parameter with the value of `Context`.
16 | */
17 | export function Context(
18 | ...pipes: (Type | PipeTransform)[]
19 | ): ParameterDecorator;
20 | /**
21 | * Resolver method parameter decorator. Extracts the `Context`
22 | * object from the underlying platform and populates the decorated
23 | * parameter with the value of `Context`.
24 | */
25 | export function Context(
26 | property: string,
27 | ...pipes: (Type | PipeTransform)[]
28 | ): ParameterDecorator;
29 | /**
30 | * Resolver method parameter decorator. Extracts the `Context`
31 | * object from the underlying platform and populates the decorated
32 | * parameter with the value of `Context`.
33 | */
34 | export function Context(
35 | property?: string | (Type | PipeTransform),
36 | ...pipes: (Type | PipeTransform)[]
37 | ): ParameterDecorator {
38 | return createGqlPipesParamDecorator(GqlParamtype.CONTEXT)(property, ...pipes);
39 | }
40 |
--------------------------------------------------------------------------------
/lib/decorators/directive.decorator.ts:
--------------------------------------------------------------------------------
1 | import { parse } from 'graphql';
2 | import { DirectiveParsingError } from '../schema-builder/errors/directive-parsing.error';
3 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
4 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
5 |
6 | /**
7 | * Adds a directive to specified field, type, or handler.
8 | */
9 | export function Directive(
10 | sdl: string,
11 | ): MethodDecorator & PropertyDecorator & ClassDecorator {
12 | return (target: Function | Object, key?: string | symbol) => {
13 | validateDirective(sdl);
14 |
15 | LazyMetadataStorage.store(() => {
16 | if (key) {
17 | TypeMetadataStorage.addDirectivePropertyMetadata({
18 | target: target.constructor,
19 | fieldName: key as string,
20 | sdl,
21 | });
22 | } else {
23 | TypeMetadataStorage.addDirectiveMetadata({
24 | target: target as Function,
25 | sdl,
26 | });
27 | }
28 | });
29 | };
30 | }
31 |
32 | function validateDirective(sdl: string) {
33 | try {
34 | parse(`type String ${sdl}`);
35 | } catch (err) {
36 | throw new DirectiveParsingError(sdl);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/decorators/extensions.decorator.ts:
--------------------------------------------------------------------------------
1 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
2 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
3 |
4 | /**
5 | * Adds arbitrary data accessible through the "extensions" property to specified field, type, or handler.
6 | */
7 | export function Extensions(
8 | value: Record,
9 | ): MethodDecorator & ClassDecorator & PropertyDecorator {
10 | return (target: Function | object, propertyKey?: string | symbol) => {
11 | LazyMetadataStorage.store(() => {
12 | if (propertyKey) {
13 | TypeMetadataStorage.addExtensionsPropertyMetadata({
14 | target: target.constructor,
15 | fieldName: propertyKey as string,
16 | value,
17 | });
18 | } else {
19 | TypeMetadataStorage.addExtensionsMetadata({
20 | target: target as Function,
21 | value,
22 | });
23 | }
24 | });
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/lib/decorators/field.decorator.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The API surface of this module has been heavily inspired by the "type-graphql" library (https://github.com/MichalLytek/type-graphql), originally designed & released by Michal Lytek.
3 | * In the v6 major release of NestJS, we introduced the code-first approach as a compatibility layer between this package and the `@nestjs/graphql` module.
4 | * Eventually, our team decided to reimplement all the features from scratch due to a lack of flexibility.
5 | * To avoid numerous breaking changes, the public API is backward-compatible and may resemble "type-graphql".
6 | */
7 |
8 | import { isFunction } from '@nestjs/common/utils/shared.utils';
9 | import { Complexity } from '../interfaces';
10 | import { BaseTypeOptions } from '../interfaces/base-type-options.interface';
11 | import { ReturnTypeFunc } from '../interfaces/return-type-func.interface';
12 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
13 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
14 | import { reflectTypeFromMetadata } from '../utils/reflection.utilts';
15 |
16 | /**
17 | * Interface defining options that can be passed to `@Field()` decorator.
18 | */
19 | export interface FieldOptions extends BaseTypeOptions {
20 | /**
21 | * Name of the field.
22 | */
23 | name?: string;
24 | /**
25 | * Description of the field.
26 | */
27 | description?: string;
28 | /**
29 | * Field deprecation reason (if deprecated).
30 | */
31 | deprecationReason?: string;
32 | /**
33 | * Field complexity options.
34 | */
35 | complexity?: Complexity;
36 | }
37 |
38 | /**
39 | * @Field() decorator is used to mark a specific class property as a GraphQL field.
40 | * Only properties decorated with this decorator will be defined in the schema.
41 | */
42 | export function Field(): PropertyDecorator & MethodDecorator;
43 | /**
44 | * @Field() decorator is used to mark a specific class property as a GraphQL field.
45 | * Only properties decorated with this decorator will be defined in the schema.
46 | */
47 | export function Field(
48 | options: FieldOptions,
49 | ): PropertyDecorator & MethodDecorator;
50 | /**
51 | * @Field() decorator is used to mark a specific class property as a GraphQL field.
52 | * Only properties decorated with this decorator will be defined in the schema.
53 | */
54 | export function Field(
55 | returnTypeFunction?: ReturnTypeFunc,
56 | options?: FieldOptions,
57 | ): PropertyDecorator & MethodDecorator;
58 | /**
59 | * @Field() decorator is used to mark a specific class property as a GraphQL field.
60 | * Only properties decorated with this decorator will be defined in the schema.
61 | */
62 | export function Field(
63 | typeOrOptions?: ReturnTypeFunc | FieldOptions,
64 | fieldOptions?: FieldOptions,
65 | ): PropertyDecorator & MethodDecorator {
66 | return (
67 | prototype: Object,
68 | propertyKey?: string,
69 | descriptor?: TypedPropertyDescriptor,
70 | ) => {
71 | addFieldMetadata(
72 | typeOrOptions,
73 | fieldOptions,
74 | prototype,
75 | propertyKey,
76 | descriptor,
77 | );
78 | };
79 | }
80 |
81 | export function addFieldMetadata(
82 | typeOrOptions: ReturnTypeFunc | FieldOptions,
83 | fieldOptions: FieldOptions,
84 | prototype: Object,
85 | propertyKey?: string,
86 | descriptor?: TypedPropertyDescriptor,
87 | loadEagerly?: boolean,
88 | ) {
89 | const [typeFunc, options = {}] = isFunction(typeOrOptions)
90 | ? [typeOrOptions, fieldOptions]
91 | : [undefined, typeOrOptions as any];
92 |
93 | const applyMetadataFn = () => {
94 | const isResolver = !!descriptor;
95 | const isResolverMethod = !!(descriptor && descriptor.value);
96 |
97 | const { typeFn: getType, options: typeOptions } = reflectTypeFromMetadata({
98 | metadataKey: isResolverMethod ? 'design:returntype' : 'design:type',
99 | prototype,
100 | propertyKey,
101 | explicitTypeFn: typeFunc as ReturnTypeFunc,
102 | typeOptions: options,
103 | });
104 |
105 | TypeMetadataStorage.addClassFieldMetadata({
106 | name: propertyKey,
107 | schemaName: options.name || propertyKey,
108 | typeFn: getType,
109 | options: typeOptions,
110 | target: prototype.constructor,
111 | description: options.description,
112 | deprecationReason: options.deprecationReason,
113 | complexity: options.complexity,
114 | });
115 |
116 | if (isResolver) {
117 | TypeMetadataStorage.addResolverPropertyMetadata({
118 | kind: 'internal',
119 | methodName: propertyKey,
120 | schemaName: options.name || propertyKey,
121 | target: prototype.constructor,
122 | complexity: options.complexity,
123 | });
124 | }
125 | };
126 | if (loadEagerly) {
127 | applyMetadataFn();
128 | } else {
129 | LazyMetadataStorage.store(applyMetadataFn);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/lib/decorators/hide-field.decorator.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unused-vars */
2 | /* eslint-disable @typescript-eslint/no-empty-function */
3 | export function HideField(): PropertyDecorator {
4 | return (target: Record, propertyKey: string | symbol) => {};
5 | }
6 |
--------------------------------------------------------------------------------
/lib/decorators/index.ts:
--------------------------------------------------------------------------------
1 | export * from './args-type.decorator';
2 | export * from './args.decorator';
3 | export * from './context.decorator';
4 | export * from './directive.decorator';
5 | export * from './extensions.decorator';
6 | export * from './field.decorator';
7 | export * from './hide-field.decorator';
8 | export * from './info.decorator';
9 | export * from './input-type.decorator';
10 | export * from './interface-type.decorator';
11 | export * from './mutation.decorator';
12 | export * from './object-type.decorator';
13 | export * from './parent.decorator';
14 | export * from './query.decorator';
15 | export * from './resolve-field.decorator';
16 | export * from './resolver.decorator';
17 | export * from './root.decorator';
18 | export * from './scalar.decorator';
19 | export * from './subscription.decorator';
20 |
--------------------------------------------------------------------------------
/lib/decorators/info.decorator.ts:
--------------------------------------------------------------------------------
1 | import { PipeTransform, Type } from '@nestjs/common';
2 | import 'reflect-metadata';
3 | import { GqlParamtype } from '../enums/gql-paramtype.enum';
4 | import { createGqlPipesParamDecorator } from './param.utils';
5 |
6 | /**
7 | * Resolver method parameter decorator. Extracts the `Info`
8 | * object from the underlying platform and populates the decorated
9 | * parameter with the value of `Info`.
10 | */
11 | export function Info(...pipes: (Type | PipeTransform)[]) {
12 | return createGqlPipesParamDecorator(GqlParamtype.INFO)(undefined, ...pipes);
13 | }
14 |
--------------------------------------------------------------------------------
/lib/decorators/input-type.decorator.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The API surface of this module has been heavily inspired by the "type-graphql" library (https://github.com/MichalLytek/type-graphql), originally designed & released by Michal Lytek.
3 | * In the v6 major release of NestJS, we introduced the code-first approach as a compatibility layer between this package and the `@nestjs/graphql` module.
4 | * Eventually, our team decided to reimplement all the features from scratch due to a lack of flexibility.
5 | * To avoid numerous breaking changes, the public API is backward-compatible and may resemble "type-graphql".
6 | */
7 |
8 | import { isString } from '@nestjs/common/utils/shared.utils';
9 | import { ClassType } from '../enums/class-type.enum';
10 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
11 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
12 | import { addClassTypeMetadata } from '../utils/add-class-type-metadata.util';
13 |
14 | /**
15 | * Interface defining options that can be passed to `@InputType()` decorator.
16 | */
17 | export interface InputTypeOptions {
18 | /**
19 | * Description of the input type.
20 | */
21 | description?: string;
22 | /**
23 | * If `true`, type will not be registered in the schema.
24 | */
25 | isAbstract?: boolean;
26 | }
27 |
28 | /**
29 | * Decorator that marks a class as a GraphQL input type.
30 | */
31 | export function InputType(): ClassDecorator;
32 | /**
33 | * Decorator that marks a class as a GraphQL input type.
34 | */
35 | export function InputType(options: InputTypeOptions): ClassDecorator;
36 | /**
37 | * Decorator that marks a class as a GraphQL input type.
38 | */
39 | export function InputType(
40 | name: string,
41 | options?: InputTypeOptions,
42 | ): ClassDecorator;
43 | /**
44 | * Decorator that marks a class as a GraphQL input type.
45 | */
46 | export function InputType(
47 | nameOrOptions?: string | InputTypeOptions,
48 | inputTypeOptions?: InputTypeOptions,
49 | ): ClassDecorator {
50 | const [name, options = {}] = isString(nameOrOptions)
51 | ? [nameOrOptions, inputTypeOptions]
52 | : [undefined, nameOrOptions];
53 |
54 | return (target) => {
55 | const metadata = {
56 | target,
57 | name: name || target.name,
58 | description: options.description,
59 | isAbstract: options.isAbstract,
60 | };
61 | LazyMetadataStorage.store(() =>
62 | TypeMetadataStorage.addInputTypeMetadata(metadata),
63 | );
64 | addClassTypeMetadata(target, ClassType.INPUT);
65 | };
66 | }
67 |
--------------------------------------------------------------------------------
/lib/decorators/interface-type.decorator.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The API surface of this module has been heavily inspired by the "type-graphql" library (https://github.com/MichalLytek/type-graphql), originally designed & released by Michal Lytek.
3 | * In the v6 major release of NestJS, we introduced the code-first approach as a compatibility layer between this package and the `@nestjs/graphql` module.
4 | * Eventually, our team decided to reimplement all the features from scratch due to a lack of flexibility.
5 | * To avoid numerous breaking changes, the public API is backward-compatible and may resemble "type-graphql".
6 | */
7 |
8 | import { isString } from '@nestjs/common/utils/shared.utils';
9 | import { ClassType } from '../enums/class-type.enum';
10 | import { ResolveTypeFn } from '../interfaces';
11 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
12 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
13 | import { addClassTypeMetadata } from '../utils/add-class-type-metadata.util';
14 |
15 | /**
16 | * Interface defining options that can be passed to `@InterfaceType()` decorator.
17 | */
18 | export interface InterfaceTypeOptions {
19 | /**
20 | * Description of the argument.
21 | */
22 | description?: string;
23 | /**
24 | * If `true`, type will not be registered in the schema.
25 | */
26 | isAbstract?: boolean;
27 | /**
28 | * Custom implementation of the "resolveType" function.
29 | */
30 | resolveType?: ResolveTypeFn;
31 | }
32 |
33 | /**
34 | * Decorator that marks a class as a GraphQL interface type.
35 | */
36 | export function InterfaceType(options?: InterfaceTypeOptions): ClassDecorator;
37 | /**
38 | * Decorator that marks a class as a GraphQL interface type.
39 | */
40 | export function InterfaceType(
41 | name: string,
42 | options?: InterfaceTypeOptions,
43 | ): ClassDecorator;
44 | /**
45 | * Decorator that marks a class as a GraphQL interface type.
46 | */
47 | export function InterfaceType(
48 | nameOrOptions?: string | InterfaceTypeOptions,
49 | interfaceOptions?: InterfaceTypeOptions,
50 | ): ClassDecorator {
51 | const [name, options = {}] = isString(nameOrOptions)
52 | ? [nameOrOptions, interfaceOptions]
53 | : [undefined, nameOrOptions];
54 |
55 | return (target) => {
56 | const metadata = {
57 | name: name || target.name,
58 | target,
59 | ...options,
60 | };
61 | LazyMetadataStorage.store(() =>
62 | TypeMetadataStorage.addInterfaceMetadata(metadata),
63 | );
64 |
65 | addClassTypeMetadata(target, ClassType.INTERFACE);
66 | };
67 | }
68 |
--------------------------------------------------------------------------------
/lib/decorators/mutation.decorator.ts:
--------------------------------------------------------------------------------
1 | import { Type } from '@nestjs/common';
2 | import { isString } from '@nestjs/common/utils/shared.utils';
3 | import 'reflect-metadata';
4 | import { Resolver } from '../enums/resolver.enum';
5 | import { BaseTypeOptions } from '../interfaces/base-type-options.interface';
6 | import { ReturnTypeFunc } from '../interfaces/return-type-func.interface';
7 | import { UndefinedReturnTypeError } from '../schema-builder/errors/undefined-return-type.error';
8 | import { ResolverTypeMetadata } from '../schema-builder/metadata';
9 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
10 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
11 | import { reflectTypeFromMetadata } from '../utils/reflection.utilts';
12 | import { addResolverMetadata } from './resolvers.utils';
13 |
14 | /**
15 | * Interface defining options that can be passed to `@Mutation()` decorator.
16 | */
17 | export interface MutationOptions extends BaseTypeOptions {
18 | /**
19 | * Name of the mutation.
20 | */
21 | name?: string;
22 | /**
23 | * Description of the mutation.
24 | */
25 | description?: string;
26 | /**
27 | * Mutation deprecation reason (if deprecated).
28 | */
29 | deprecationReason?: string;
30 | }
31 |
32 | /**
33 | * Mutation handler (method) Decorator. Routes specified mutation to this method.
34 | */
35 | export function Mutation(): MethodDecorator;
36 | /**
37 | * Mutation handler (method) Decorator. Routes specified mutation to this method.
38 | */
39 | export function Mutation(name: string): MethodDecorator;
40 | /**
41 | * Mutation handler (method) Decorator. Routes specified mutation to this method.
42 | */
43 | export function Mutation(
44 | typeFunc: ReturnTypeFunc,
45 | options?: MutationOptions,
46 | ): MethodDecorator;
47 | /**
48 | * Mutation handler (method) Decorator. Routes specified mutation to this method.
49 | */
50 | export function Mutation(
51 | nameOrType?: string | ReturnTypeFunc,
52 | options: MutationOptions = {},
53 | ): MethodDecorator {
54 | return (target: Object | Function, key?: string, descriptor?: any) => {
55 | const name = isString(nameOrType)
56 | ? nameOrType
57 | : (options && options.name) || undefined;
58 |
59 | addResolverMetadata(Resolver.MUTATION, name, target, key, descriptor);
60 |
61 | LazyMetadataStorage.store(target.constructor as Type, () => {
62 | if (!nameOrType || isString(nameOrType)) {
63 | throw new UndefinedReturnTypeError(Mutation.name, key);
64 | }
65 |
66 | const { typeFn, options: typeOptions } = reflectTypeFromMetadata({
67 | metadataKey: 'design:returntype',
68 | prototype: target,
69 | propertyKey: key,
70 | explicitTypeFn: nameOrType,
71 | typeOptions: options,
72 | });
73 | const metadata: ResolverTypeMetadata = {
74 | methodName: key,
75 | schemaName: options.name || key,
76 | target: target.constructor,
77 | typeFn,
78 | returnTypeOptions: typeOptions,
79 | description: options.description,
80 | deprecationReason: options.deprecationReason,
81 | };
82 | TypeMetadataStorage.addMutationMetadata(metadata);
83 | });
84 | };
85 | }
86 |
--------------------------------------------------------------------------------
/lib/decorators/object-type.decorator.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The API surface of this module has been heavily inspired by the "type-graphql" library (https://github.com/MichalLytek/type-graphql), originally designed & released by Michal Lytek.
3 | * In the v6 major release of NestJS, we introduced the code-first approach as a compatibility layer between this package and the `@nestjs/graphql` module.
4 | * Eventually, our team decided to reimplement all the features from scratch due to a lack of flexibility.
5 | * To avoid numerous breaking changes, the public API is backward-compatible and may resemble "type-graphql".
6 | */
7 |
8 | import { isString } from '@nestjs/common/utils/shared.utils';
9 | import { ClassType } from '../enums/class-type.enum';
10 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
11 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
12 | import { addClassTypeMetadata } from '../utils/add-class-type-metadata.util';
13 |
14 | /**
15 | * Interface defining options that can be passed to `@ObjectType()` decorator
16 | */
17 | export interface ObjectTypeOptions {
18 | /**
19 | * Description of the input type.
20 | */
21 | description?: string;
22 | /**
23 | * If `true`, type will not be registered in the schema.
24 | */
25 | isAbstract?: boolean;
26 | /**
27 | * Interfaces implemented by this object.
28 | */
29 | implements?: Function | Function[];
30 | }
31 |
32 | /**
33 | * Decorator that marks a class as a GraphQL type.
34 | */
35 | export function ObjectType(): ClassDecorator;
36 | /**
37 | * Decorator that marks a class as a GraphQL type.
38 | */
39 | export function ObjectType(options: ObjectTypeOptions): ClassDecorator;
40 | /**
41 | * Decorator that marks a class as a GraphQL type.
42 | */
43 | export function ObjectType(
44 | name: string,
45 | options?: ObjectTypeOptions,
46 | ): ClassDecorator;
47 | /**
48 | * Decorator that marks a class as a GraphQL type.
49 | */
50 | export function ObjectType(
51 | nameOrOptions?: string | ObjectTypeOptions,
52 | objectTypeOptions?: ObjectTypeOptions,
53 | ): ClassDecorator {
54 | const [name, options = {}] = isString(nameOrOptions)
55 | ? [nameOrOptions, objectTypeOptions]
56 | : [undefined, nameOrOptions];
57 |
58 | const interfaces = options.implements
59 | ? [].concat(options.implements)
60 | : undefined;
61 | return (target) => {
62 | const addObjectTypeMetadata = () =>
63 | TypeMetadataStorage.addObjectTypeMetadata({
64 | name: name || target.name,
65 | target,
66 | description: options.description,
67 | interfaces,
68 | isAbstract: options.isAbstract,
69 | });
70 |
71 | // This function must be called eagerly to allow resolvers
72 | // accessing the "name" property
73 | addObjectTypeMetadata();
74 | LazyMetadataStorage.store(addObjectTypeMetadata);
75 |
76 | addClassTypeMetadata(target, ClassType.OBJECT);
77 | };
78 | }
79 |
--------------------------------------------------------------------------------
/lib/decorators/param.utils.ts:
--------------------------------------------------------------------------------
1 | import { PipeTransform, Type } from '@nestjs/common';
2 | import { isNil, isString } from '@nestjs/common/utils/shared.utils';
3 | import 'reflect-metadata';
4 | import { GqlParamtype } from '../enums/gql-paramtype.enum';
5 | import { PARAM_ARGS_METADATA } from '../fgql.constants';
6 |
7 | export type ParamData = object | string | number;
8 | export type ParamsMetadata = Record<
9 | number,
10 | {
11 | index: number;
12 | data?: ParamData;
13 | }
14 | >;
15 |
16 | function assignMetadata(
17 | args: ParamsMetadata,
18 | paramtype: GqlParamtype,
19 | index: number,
20 | data?: ParamData,
21 | ...pipes: (Type | PipeTransform)[]
22 | ) {
23 | return {
24 | ...args,
25 | [`${paramtype}:${index}`]: {
26 | index,
27 | data,
28 | pipes,
29 | },
30 | };
31 | }
32 |
33 | export const createGqlParamDecorator = (paramtype: GqlParamtype) => {
34 | return (data?: ParamData): ParameterDecorator => (target, key, index) => {
35 | const args =
36 | Reflect.getMetadata(PARAM_ARGS_METADATA, target.constructor, key) || {};
37 | Reflect.defineMetadata(
38 | PARAM_ARGS_METADATA,
39 | assignMetadata(args, paramtype, index, data),
40 | target.constructor,
41 | key,
42 | );
43 | };
44 | };
45 |
46 | export const addPipesMetadata = (
47 | paramtype: GqlParamtype,
48 | data: any,
49 | pipes: (Type | PipeTransform)[],
50 | target: Record,
51 | key: string | symbol,
52 | index: number,
53 | ) => {
54 | const args =
55 | Reflect.getMetadata(PARAM_ARGS_METADATA, target.constructor, key) || {};
56 | const hasParamData = isNil(data) || isString(data);
57 | const paramData = hasParamData ? data : undefined;
58 | const paramPipes = hasParamData ? pipes : [data, ...pipes];
59 |
60 | Reflect.defineMetadata(
61 | PARAM_ARGS_METADATA,
62 | assignMetadata(args, paramtype, index, paramData, ...paramPipes),
63 | target.constructor,
64 | key,
65 | );
66 | };
67 |
68 | export const createGqlPipesParamDecorator = (paramtype: GqlParamtype) => (
69 | data?: any,
70 | ...pipes: (Type | PipeTransform)[]
71 | ): ParameterDecorator => (target, key, index) => {
72 | addPipesMetadata(paramtype, data, pipes, target, key, index);
73 | };
74 |
--------------------------------------------------------------------------------
/lib/decorators/parent.decorator.ts:
--------------------------------------------------------------------------------
1 | import { GqlParamtype } from '../enums/gql-paramtype.enum';
2 | import { createGqlParamDecorator } from './param.utils';
3 |
4 | /**
5 | * Resolver method parameter decorator. Extracts the parent/root
6 | * object from the underlying platform and populates the decorated
7 | * parameter with the value of parent/root.
8 | */
9 | export const Parent: () => ParameterDecorator = createGqlParamDecorator(
10 | GqlParamtype.ROOT,
11 | );
12 |
--------------------------------------------------------------------------------
/lib/decorators/query.decorator.ts:
--------------------------------------------------------------------------------
1 | import { Type } from '@nestjs/common';
2 | import { isString } from '@nestjs/common/utils/shared.utils';
3 | import 'reflect-metadata';
4 | import { Resolver } from '../enums/resolver.enum';
5 | import { BaseTypeOptions } from '../interfaces/base-type-options.interface';
6 | import { ReturnTypeFunc } from '../interfaces/return-type-func.interface';
7 | import { UndefinedReturnTypeError } from '../schema-builder/errors/undefined-return-type.error';
8 | import { ResolverTypeMetadata } from '../schema-builder/metadata';
9 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
10 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
11 | import { reflectTypeFromMetadata } from '../utils/reflection.utilts';
12 | import { addResolverMetadata } from './resolvers.utils';
13 |
14 | /**
15 | * Interface defining options that can be passed to `@Query()` decorator.
16 | */
17 | export interface QueryOptions extends BaseTypeOptions {
18 | /**
19 | * Name of the query.
20 | */
21 | name?: string;
22 | /**
23 | * Description of the query.
24 | */
25 | description?: string;
26 | /**
27 | * Query deprecation reason (if deprecated).
28 | */
29 | deprecationReason?: string;
30 | }
31 |
32 | /**
33 | * Query handler (method) Decorator. Routes specified query to this method.
34 | */
35 | export function Query(): MethodDecorator;
36 | /**
37 | * Query handler (method) Decorator. Routes specified query to this method.
38 | */
39 | export function Query(name: string): MethodDecorator;
40 | /**
41 | * Query handler (method) Decorator. Routes specified query to this method.
42 | */
43 | export function Query(
44 | typeFunc: ReturnTypeFunc,
45 | options?: QueryOptions,
46 | ): MethodDecorator;
47 | /**
48 | * Query handler (method) Decorator. Routes specified query to this method.
49 | */
50 | export function Query(
51 | nameOrType?: string | ReturnTypeFunc,
52 | options: QueryOptions = {},
53 | ): MethodDecorator {
54 | return (target: Object | Function, key?: string, descriptor?: any) => {
55 | const name = isString(nameOrType)
56 | ? nameOrType
57 | : (options && options.name) || undefined;
58 |
59 | addResolverMetadata(Resolver.QUERY, name, target, key, descriptor);
60 |
61 | LazyMetadataStorage.store(target.constructor as Type, () => {
62 | if (!nameOrType || isString(nameOrType)) {
63 | throw new UndefinedReturnTypeError(Query.name, key);
64 | }
65 |
66 | const { typeFn, options: typeOptions } = reflectTypeFromMetadata({
67 | metadataKey: 'design:returntype',
68 | prototype: target,
69 | propertyKey: key,
70 | explicitTypeFn: nameOrType,
71 | typeOptions: options || {},
72 | });
73 | const metadata: ResolverTypeMetadata = {
74 | methodName: key,
75 | schemaName: options.name || key,
76 | target: target.constructor,
77 | typeFn,
78 | returnTypeOptions: typeOptions,
79 | description: options.description,
80 | deprecationReason: options.deprecationReason,
81 | };
82 | TypeMetadataStorage.addQueryMetadata(metadata);
83 | });
84 | };
85 | }
86 |
--------------------------------------------------------------------------------
/lib/decorators/resolve-field.decorator.ts:
--------------------------------------------------------------------------------
1 | import { SetMetadata, Type } from '@nestjs/common';
2 | import { isFunction, isObject } from '@nestjs/common/utils/shared.utils';
3 | import {
4 | RESOLVER_NAME_METADATA,
5 | RESOLVER_PROPERTY_METADATA,
6 | } from '../fgql.constants';
7 | import { Complexity } from '../interfaces';
8 | import { BaseTypeOptions } from '../interfaces/base-type-options.interface';
9 | import {
10 | GqlTypeReference,
11 | ReturnTypeFunc,
12 | } from '../interfaces/return-type-func.interface';
13 | import { TypeOptions } from '../interfaces/type-options.interface';
14 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
15 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
16 | import { reflectTypeFromMetadata } from '../utils/reflection.utilts';
17 |
18 | /**
19 | * Interface defining options that can be passed to `@ResolveField()` decorator.
20 | */
21 | export interface ResolveFieldOptions extends BaseTypeOptions {
22 | /**
23 | * Name of the field.
24 | */
25 | name?: string;
26 | /**
27 | * Description of the field.
28 | */
29 | description?: string;
30 | /**
31 | * Field deprecation reason (if deprecated).
32 | */
33 | deprecationReason?: string;
34 | /**
35 | * Field complexity options.
36 | */
37 | complexity?: Complexity;
38 | }
39 |
40 | /**
41 | * Field resolver (method) Decorator.
42 | */
43 | export function ResolveField(
44 | typeFunc?: ReturnTypeFunc,
45 | options?: ResolveFieldOptions,
46 | ): MethodDecorator;
47 | /**
48 | * Property resolver (method) Decorator.
49 | */
50 | export function ResolveField(
51 | propertyName?: string,
52 | typeFunc?: ReturnTypeFunc,
53 | options?: ResolveFieldOptions,
54 | ): MethodDecorator;
55 | /**
56 | * Property resolver (method) Decorator.
57 | */
58 | export function ResolveField(
59 | propertyNameOrFunc?: string | ReturnTypeFunc,
60 | typeFuncOrOptions?: ReturnTypeFunc | ResolveFieldOptions,
61 | resolveFieldOptions?: ResolveFieldOptions,
62 | ): MethodDecorator {
63 | return (
64 | target: Function | Record,
65 | key?: string,
66 | descriptor?: any,
67 | ) => {
68 | // eslint-disable-next-line prefer-const
69 | let [propertyName, typeFunc, options] = isFunction(propertyNameOrFunc)
70 | ? typeFuncOrOptions && typeFuncOrOptions.name
71 | ? [typeFuncOrOptions.name, propertyNameOrFunc, typeFuncOrOptions]
72 | : [undefined, propertyNameOrFunc, typeFuncOrOptions]
73 | : [propertyNameOrFunc, typeFuncOrOptions, resolveFieldOptions];
74 |
75 | SetMetadata(RESOLVER_NAME_METADATA, propertyName)(target, key, descriptor);
76 | SetMetadata(RESOLVER_PROPERTY_METADATA, true)(target, key, descriptor);
77 |
78 | options = isObject(options)
79 | ? {
80 | name: propertyName as string,
81 | ...options,
82 | }
83 | : propertyName
84 | ? { name: propertyName as string }
85 | : {};
86 |
87 | LazyMetadataStorage.store(target.constructor as Type, () => {
88 | let typeOptions: TypeOptions, typeFn: (type?: any) => GqlTypeReference;
89 | try {
90 | const implicitTypeMetadata = reflectTypeFromMetadata({
91 | metadataKey: 'design:returntype',
92 | prototype: target,
93 | propertyKey: key,
94 | explicitTypeFn: typeFunc as ReturnTypeFunc,
95 | typeOptions: options as any,
96 | });
97 | typeOptions = implicitTypeMetadata.options;
98 | typeFn = implicitTypeMetadata.typeFn;
99 | } catch {}
100 |
101 | TypeMetadataStorage.addResolverPropertyMetadata({
102 | kind: 'external',
103 | methodName: key,
104 | schemaName: options.name || key,
105 | target: target.constructor,
106 | typeFn,
107 | typeOptions,
108 | description: (options as ResolveFieldOptions).description,
109 | deprecationReason: (options as ResolveFieldOptions).deprecationReason,
110 | complexity: (options as ResolveFieldOptions).complexity,
111 | });
112 | });
113 | };
114 | }
115 |
--------------------------------------------------------------------------------
/lib/decorators/resolver.decorator.ts:
--------------------------------------------------------------------------------
1 | import { Type } from '@nestjs/common';
2 | import { isFunction, isString } from '@nestjs/common/utils/shared.utils';
3 | import 'reflect-metadata';
4 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
5 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
6 | import {
7 | addResolverMetadata,
8 | getClassName,
9 | getClassOrUndefined,
10 | getResolverTypeFn,
11 | } from './resolvers.utils';
12 |
13 | export type ResolverTypeFn = (of?: void) => Type;
14 |
15 | /**
16 | * Extracts the name property set through the @ObjectType() decorator (if specified)
17 | * @param nameOrType type reference
18 | */
19 | function getObjectTypeNameIfExists(nameOrType: Function): string | undefined {
20 | const ctor = getClassOrUndefined(nameOrType);
21 | const objectTypesMetadata = TypeMetadataStorage.getObjectTypesMetadata();
22 | const objectMetadata = objectTypesMetadata.find(type => type.target === ctor);
23 | if (!objectMetadata) {
24 | return;
25 | }
26 | return objectMetadata.name;
27 | }
28 |
29 | /**
30 | * Interface defining options that can be passed to `@Resolve()` decorator
31 | */
32 | export interface ResolverOptions {
33 | /**
34 | * If `true`, type will not be registered in the schema.
35 | */
36 | isAbstract?: boolean;
37 | }
38 |
39 | /**
40 | * Object resolver decorator.
41 | */
42 | export function Resolver(): MethodDecorator & ClassDecorator;
43 | /**
44 | * Object resolver decorator.
45 | */
46 | export function Resolver(name: string): MethodDecorator & ClassDecorator;
47 | /**
48 | * Object resolver decorator.
49 | */
50 | export function Resolver(
51 | options: ResolverOptions,
52 | ): MethodDecorator & ClassDecorator;
53 | /**
54 | * Object resolver decorator.
55 | */
56 | export function Resolver(
57 | classType: Type,
58 | options?: ResolverOptions,
59 | ): MethodDecorator & ClassDecorator;
60 | /**
61 | * Object resolver decorator.
62 | */
63 | export function Resolver(
64 | typeFunc: ResolverTypeFn,
65 | options?: ResolverOptions,
66 | ): MethodDecorator & ClassDecorator;
67 | /**
68 | * Object resolver decorator.
69 | */
70 | export function Resolver(
71 | nameOrTypeOrOptions?: string | ResolverTypeFn | Type | ResolverOptions,
72 | options?: ResolverOptions,
73 | ): MethodDecorator & ClassDecorator {
74 | return (
75 | target: Object | Function,
76 | key?: string | symbol,
77 | descriptor?: any,
78 | ) => {
79 | const [nameOrType, resolverOptions] =
80 | typeof nameOrTypeOrOptions === 'object' && nameOrTypeOrOptions !== null
81 | ? [undefined, nameOrTypeOrOptions]
82 | : [nameOrTypeOrOptions as string | ResolverTypeFn | Type, options];
83 |
84 | let name = nameOrType && getClassName(nameOrType);
85 |
86 | if (isFunction(nameOrType)) {
87 | const objectName = getObjectTypeNameIfExists(nameOrType as Function);
88 | objectName && (name = objectName);
89 | }
90 | addResolverMetadata(undefined, name, target, key, descriptor);
91 |
92 | if (!isString(nameOrType)) {
93 | LazyMetadataStorage.store(target as Type, () => {
94 | const typeFn = getResolverTypeFn(nameOrType, target as Function);
95 |
96 | TypeMetadataStorage.addResolverMetadata({
97 | target: target as Function,
98 | typeFn: typeFn,
99 | isAbstract: (resolverOptions && resolverOptions.isAbstract) || false,
100 | });
101 | });
102 | }
103 | };
104 | }
105 |
--------------------------------------------------------------------------------
/lib/decorators/resolvers.utils.ts:
--------------------------------------------------------------------------------
1 | import { SetMetadata, Type } from '@nestjs/common';
2 | import { isFunction, isString } from '@nestjs/common/utils/shared.utils';
3 | import { Resolver } from '../enums/resolver.enum';
4 | import {
5 | RESOLVER_NAME_METADATA,
6 | RESOLVER_TYPE_METADATA,
7 | } from '../fgql.constants';
8 | import { UndefinedResolverTypeError } from '../schema-builder/errors/undefined-resolver-type.error';
9 | import { ResolverTypeFn } from './resolver.decorator';
10 |
11 | export function addResolverMetadata(
12 | resolver: Resolver | string | undefined,
13 | name: string | undefined,
14 | target?: Record | Function,
15 | key?: string | symbol,
16 | descriptor?: any,
17 | ) {
18 | SetMetadata(RESOLVER_TYPE_METADATA, resolver || name)(
19 | target,
20 | key,
21 | descriptor,
22 | );
23 | SetMetadata(RESOLVER_NAME_METADATA, name)(target, key, descriptor);
24 | }
25 |
26 | export function getClassName(nameOrType: string | Function | Type) {
27 | if (isString(nameOrType)) {
28 | return nameOrType;
29 | }
30 | const classOrUndefined = getClassOrUndefined(nameOrType);
31 | return classOrUndefined && classOrUndefined.name;
32 | }
33 |
34 | export function getResolverTypeFn(nameOrType: Function, target: Function) {
35 | return nameOrType
36 | ? nameOrType.prototype
37 | ? () => nameOrType as Type
38 | : (nameOrType as ResolverTypeFn)
39 | : () => {
40 | throw new UndefinedResolverTypeError(target.name);
41 | };
42 | }
43 |
44 | export function getClassOrUndefined(typeOrFunc: Function | Type) {
45 | return isConstructor(typeOrFunc)
46 | ? typeOrFunc
47 | : isFunction(typeOrFunc)
48 | ? (typeOrFunc as Function)()
49 | : undefined;
50 | }
51 |
52 | function isConstructor(obj: any) {
53 | return (
54 | !!obj.prototype &&
55 | !!obj.prototype.constructor &&
56 | !!obj.prototype.constructor.name
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/lib/decorators/root.decorator.ts:
--------------------------------------------------------------------------------
1 | import { GqlParamtype } from '../enums/gql-paramtype.enum';
2 | import { createGqlParamDecorator } from './param.utils';
3 |
4 | /**
5 | * Resolver method parameter decorator. Extracts the parent/root
6 | * object from the underlying platform and populates the decorated
7 | * parameter with the value of parent/root.
8 | */
9 | export const Root: () => ParameterDecorator = createGqlParamDecorator(
10 | GqlParamtype.ROOT,
11 | );
12 |
--------------------------------------------------------------------------------
/lib/decorators/scalar.decorator.ts:
--------------------------------------------------------------------------------
1 | import { SetMetadata } from '@nestjs/common';
2 | import {
3 | SCALAR_NAME_METADATA,
4 | SCALAR_TYPE_METADATA,
5 | } from '../fgql.constants';
6 | import { ReturnTypeFunc } from '../interfaces/return-type-func.interface';
7 |
8 | /**
9 | * Decorator that marks a class as a GraphQL scalar.
10 | */
11 | export function Scalar(name: string): ClassDecorator;
12 | /**
13 | * Decorator that marks a class as a GraphQL scalar.
14 | */
15 | export function Scalar(name: string, typeFunc: ReturnTypeFunc): ClassDecorator;
16 | /**
17 | * Decorator that marks a class as a GraphQL scalar.
18 | */
19 | export function Scalar(
20 | name: string,
21 | typeFunc?: ReturnTypeFunc,
22 | ): ClassDecorator {
23 | return (target, key?, descriptor?) => {
24 | SetMetadata(SCALAR_NAME_METADATA, name)(target, key, descriptor);
25 | SetMetadata(SCALAR_TYPE_METADATA, typeFunc)(target, key, descriptor);
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/lib/decorators/subscription.decorator.ts:
--------------------------------------------------------------------------------
1 | import { SetMetadata, Type } from '@nestjs/common';
2 | import { isString } from '@nestjs/common/utils/shared.utils';
3 | import 'reflect-metadata';
4 | import { Resolver } from '../enums/resolver.enum';
5 | import { SUBSCRIPTION_OPTIONS_METADATA } from '../fgql.constants';
6 | import { BaseTypeOptions, ReturnTypeFunc } from '../interfaces';
7 | import { UndefinedReturnTypeError } from '../schema-builder/errors/undefined-return-type.error';
8 | import { ResolverTypeMetadata } from '../schema-builder/metadata';
9 | import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
10 | import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
11 | import { reflectTypeFromMetadata } from '../utils/reflection.utilts';
12 | import { addResolverMetadata } from './resolvers.utils';
13 |
14 | /**
15 | * Interface defining options that can be passed to `@Subscription()` decorator.
16 | */
17 | export interface SubscriptionOptions extends BaseTypeOptions {
18 | /**
19 | * Name of the subscription.
20 | */
21 | name?: string;
22 | /**
23 | * Description of the subscription.
24 | */
25 | description?: string;
26 | /**
27 | * Subscription deprecation reason (if deprecated).
28 | */
29 | deprecationReason?: string;
30 | /**
31 | * Filter messages function.
32 | */
33 | filter?: (
34 | payload: any,
35 | variables: any,
36 | context: any,
37 | ) => boolean | Promise;
38 | /**
39 | * Resolve messages function (to transform payload/message shape).
40 | */
41 | resolve?: (
42 | payload: any,
43 | args: any,
44 | context: any,
45 | info: any,
46 | ) => any | Promise;
47 | }
48 |
49 | /**
50 | * Subscription handler (method) Decorator. Routes subscriptions to this method.
51 | */
52 | export function Subscription(): MethodDecorator;
53 | /**
54 | * Subscription handler (method) Decorator. Routes subscriptions to this method.
55 | */
56 | export function Subscription(name: string): MethodDecorator;
57 | /**
58 | * Subscription handler (method) Decorator. Routes subscriptions to this method.
59 | */
60 | export function Subscription(
61 | name: string,
62 | options: Pick,
63 | ): MethodDecorator;
64 | /**
65 | * Subscription handler (method) Decorator. Routes subscriptions to this method.
66 | */
67 | export function Subscription(
68 | typeFunc: ReturnTypeFunc,
69 | options?: SubscriptionOptions,
70 | ): MethodDecorator;
71 | /**
72 | * Subscription handler (method) Decorator. Routes subscriptions to this method.
73 | */
74 | export function Subscription(
75 | nameOrType?: string | ReturnTypeFunc,
76 | options: SubscriptionOptions = {},
77 | ): MethodDecorator {
78 | return (
79 | target: Record | Function,
80 | key?: string,
81 | descriptor?: any,
82 | ) => {
83 | const name = isString(nameOrType)
84 | ? nameOrType
85 | : (options && options.name) || undefined;
86 |
87 | addResolverMetadata(Resolver.SUBSCRIPTION, name, target, key, descriptor);
88 | SetMetadata(SUBSCRIPTION_OPTIONS_METADATA, options)(
89 | target,
90 | key,
91 | descriptor,
92 | );
93 |
94 | LazyMetadataStorage.store(target.constructor as Type, () => {
95 | if (!nameOrType || isString(nameOrType)) {
96 | throw new UndefinedReturnTypeError(Subscription.name, key);
97 | }
98 |
99 | const { typeFn, options: typeOptions } = reflectTypeFromMetadata({
100 | metadataKey: 'design:returntype',
101 | prototype: target,
102 | propertyKey: key,
103 | explicitTypeFn: nameOrType,
104 | typeOptions: options,
105 | });
106 | const metadata: ResolverTypeMetadata = {
107 | methodName: key,
108 | schemaName: options.name || key,
109 | target: target.constructor,
110 | typeFn,
111 | returnTypeOptions: typeOptions,
112 | description: options.description,
113 | deprecationReason: options.deprecationReason,
114 | };
115 | TypeMetadataStorage.addSubscriptionMetadata(metadata);
116 | });
117 | };
118 | }
119 |
--------------------------------------------------------------------------------
/lib/enums/class-type.enum.ts:
--------------------------------------------------------------------------------
1 | export enum ClassType {
2 | ARGS = 'args',
3 | OBJECT = 'objectType',
4 | INPUT = 'inputType',
5 | INTERFACE = 'interface',
6 | }
7 |
--------------------------------------------------------------------------------
/lib/enums/gql-paramtype.enum.ts:
--------------------------------------------------------------------------------
1 | export enum GqlParamtype {
2 | ROOT,
3 | ARGS = 3,
4 | CONTEXT = 1,
5 | INFO = 2,
6 | }
7 |
--------------------------------------------------------------------------------
/lib/enums/resolver.enum.ts:
--------------------------------------------------------------------------------
1 | export enum Resolver {
2 | QUERY = 'Query',
3 | MUTATION = 'Mutation',
4 | SUBSCRIPTION = 'Subscription',
5 | }
6 |
--------------------------------------------------------------------------------
/lib/factories/params.factory.ts:
--------------------------------------------------------------------------------
1 | import { ParamData } from '@nestjs/common';
2 | import { ParamsFactory } from '@nestjs/core/helpers/external-context-creator';
3 | import { GqlParamtype } from '../enums/gql-paramtype.enum';
4 |
5 | export class GqlParamsFactory implements ParamsFactory {
6 | exchangeKeyForValue(type: number, data: ParamData, args: any) {
7 | if (!args) {
8 | return null;
9 | }
10 | switch (type as GqlParamtype) {
11 | case GqlParamtype.ROOT:
12 | return args[0];
13 | case GqlParamtype.ARGS:
14 | return data && args[1] ? args[1][data as string] : args[1];
15 | case GqlParamtype.CONTEXT:
16 | return data && args[2] ? args[2][data as string] : args[2];
17 | case GqlParamtype.INFO:
18 | return data && args[3] ? args[3][data as string] : args[3];
19 | default:
20 | return null;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/fgql.constants.ts:
--------------------------------------------------------------------------------
1 | export const GRAPHQL_MODULE_OPTIONS = 'FgqlModuleOptions';
2 |
3 | export const RESOLVER_TYPE_METADATA = 'graphql:resolver_type';
4 | export const RESOLVER_NAME_METADATA = 'graphql:resolver_name';
5 | export const RESOLVER_PROPERTY_METADATA = 'graphql:resolve_property';
6 | export const RESOLVER_DELEGATE_METADATA = 'graphql:delegate_property';
7 | export const SCALAR_NAME_METADATA = 'graphql:scalar_name';
8 | export const SCALAR_TYPE_METADATA = 'graphql:scalar_type';
9 | export const PLUGIN_METADATA = 'graphql:plugin';
10 | export const PARAM_ARGS_METADATA = '__routeArguments__';
11 | export const SUBSCRIPTION_OPTIONS_METADATA = 'graphql:subscription_options;';
12 | export const CLASS_TYPE_METADATA = 'graphql:class_type';
13 |
14 | export const FIELD_TYPENAME = '__resolveType';
15 | export const GRAPHQL_MODULE_ID = 'GqlModuleId';
16 | export const SUBSCRIPTION_TYPE = 'Subscription';
17 |
18 | export const DEFINITIONS_FILE_HEADER = `
19 | /** ------------------------------------------------------
20 | * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
21 | * -------------------------------------------------------
22 | */
23 |
24 | /* tslint:disable */
25 | /* eslint-disable */\n`;
26 |
27 | export const SDL_FILE_HEADER = `\
28 | # ------------------------------------------------------
29 | # THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
30 | # ------------------------------------------------------
31 |
32 | `;
33 |
--------------------------------------------------------------------------------
/lib/fgql.module.ts:
--------------------------------------------------------------------------------
1 | import {
2 | DynamicModule,
3 | Inject,
4 | Module,
5 | OnModuleInit,
6 | Provider,
7 | } from '@nestjs/common';
8 | import { HttpAdapterHost, MetadataScanner } from '@nestjs/core';
9 | import * as GQL from 'fastify-gql';
10 | import { printSchema } from 'graphql';
11 | import { GRAPHQL_MODULE_ID, GRAPHQL_MODULE_OPTIONS } from './fgql.constants';
12 | import {
13 | GraphQLAstExplorer,
14 | GraphQLFactory,
15 | GraphQLSchemaBuilder,
16 | GraphQLSchemaHost,
17 | GraphQLTypesLoader,
18 | } from './graphql';
19 | import {
20 | FgqlModuleOptions,
21 | GqlModuleAsyncOptions,
22 | GqlOptionsFactory,
23 | } from './interfaces/fgql-module-options.interface';
24 | import { GraphQLSchemaBuilderModule } from './schema-builder';
25 | import { ResolversExplorerService, ScalarsExplorerService } from './services';
26 | import { extend, generateString, mergeDefaults } from './utils';
27 |
28 | @Module({
29 | imports: [GraphQLSchemaBuilderModule],
30 | providers: [
31 | GraphQLFactory,
32 | MetadataScanner,
33 | ResolversExplorerService,
34 | ScalarsExplorerService,
35 | GraphQLAstExplorer,
36 | GraphQLTypesLoader,
37 | GraphQLSchemaBuilder,
38 | GraphQLSchemaHost,
39 | ],
40 | exports: [GraphQLTypesLoader, GraphQLAstExplorer, GraphQLSchemaHost],
41 | })
42 | export class FgqlModule implements OnModuleInit {
43 | constructor(
44 | private readonly httpAdapterHost: HttpAdapterHost,
45 | private readonly graphqlFactory: GraphQLFactory,
46 | private readonly graphqlTypesLoader: GraphQLTypesLoader,
47 | @Inject(GRAPHQL_MODULE_OPTIONS)
48 | private readonly options: FgqlModuleOptions,
49 | ) {}
50 |
51 | static forRoot(options: FgqlModuleOptions = {}): DynamicModule {
52 | options = mergeDefaults(options);
53 | return {
54 | module: FgqlModule,
55 | providers: [
56 | {
57 | provide: GRAPHQL_MODULE_OPTIONS,
58 | useValue: options,
59 | },
60 | ],
61 | };
62 | }
63 |
64 | static forRootAsync(options: GqlModuleAsyncOptions): DynamicModule {
65 | return {
66 | module: FgqlModule,
67 | imports: options.imports,
68 | providers: [
69 | ...this.createAsyncProviders(options),
70 | {
71 | provide: GRAPHQL_MODULE_ID,
72 | useValue: generateString(),
73 | },
74 | ],
75 | };
76 | }
77 |
78 | private static createAsyncProviders(
79 | options: GqlModuleAsyncOptions,
80 | ): Provider[] {
81 | if (options.useExisting || options.useFactory) {
82 | return [this.createAsyncOptionsProvider(options)];
83 | }
84 | return [
85 | this.createAsyncOptionsProvider(options),
86 | {
87 | provide: options.useClass,
88 | useClass: options.useClass,
89 | },
90 | ];
91 | }
92 |
93 | private static createAsyncOptionsProvider(
94 | options: GqlModuleAsyncOptions,
95 | ): Provider {
96 | if (options.useFactory) {
97 | return {
98 | provide: GRAPHQL_MODULE_OPTIONS,
99 | useFactory: async (...args: any[]) =>
100 | mergeDefaults(await options.useFactory(...args)),
101 | inject: options.inject || [],
102 | };
103 | }
104 | return {
105 | provide: GRAPHQL_MODULE_OPTIONS,
106 | useFactory: async (optionsFactory: GqlOptionsFactory) =>
107 | mergeDefaults(await optionsFactory.createGqlOptions()),
108 | inject: [options.useExisting || options.useClass],
109 | };
110 | }
111 |
112 | async onModuleInit() {
113 | if (!this.httpAdapterHost) {
114 | return;
115 | }
116 | const httpAdapter = this.httpAdapterHost.httpAdapter;
117 | if (!httpAdapter) {
118 | return;
119 | }
120 | const typeDefs =
121 | (await this.graphqlTypesLoader.mergeTypesByPaths(
122 | this.options.typePaths,
123 | )) || [];
124 |
125 | const mergedTypeDefs = extend(typeDefs, this.options.typeDefs);
126 | const options = await this.graphqlFactory.mergeOptions({
127 | ...this.options,
128 | typeDefs: mergedTypeDefs,
129 | });
130 |
131 | if (this.options.definitions && this.options.definitions.path) {
132 | await this.graphqlFactory.generateDefinitions(
133 | printSchema(options.schema),
134 | this.options,
135 | );
136 | }
137 |
138 | this.registerGqlServer(options);
139 | }
140 |
141 | private registerGqlServer(options: FgqlModuleOptions) {
142 | const httpAdapter = this.httpAdapterHost.httpAdapter;
143 | const platformName = httpAdapter.getType();
144 |
145 | if (platformName !== 'fastify') {
146 | throw new Error(`No support for current HttpAdapter: ${platformName}`);
147 | }
148 |
149 | const app = httpAdapter.getInstance();
150 |
151 | app.register(GQL, {
152 | graphiql: true,
153 | jit: 1,
154 | ...options,
155 | });
156 |
157 | app.addHook('preHandler', async (request, reply) => {
158 | // make sure that operationName is null, if empty string is passed
159 | if (request?.body?.operationName === '') {
160 | request.body.operationName = null;
161 | }
162 | return;
163 | });
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/lib/graphql/graphql-schema.builder.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { isString } from '@nestjs/common/utils/shared.utils';
3 | import { GraphQLSchema, printSchema, specifiedDirectives } from 'graphql';
4 | import { resolve } from 'path';
5 | import { SDL_FILE_HEADER } from '../fgql.constants';
6 | import { BuildSchemaOptions } from '../interfaces/build-schema-options.interface';
7 | import { FgqlModuleOptions } from '../interfaces/fgql-module-options.interface';
8 | import { GraphQLSchemaFactory } from '../schema-builder/graphql-schema.factory';
9 | import { FileSystemHelper } from '../schema-builder/helpers/file-system.helper';
10 | import { ScalarsExplorerService } from '../services';
11 |
12 | @Injectable()
13 | export class GraphQLSchemaBuilder {
14 | constructor(
15 | private readonly scalarsExplorerService: ScalarsExplorerService,
16 | private readonly gqlSchemaFactory: GraphQLSchemaFactory,
17 | private readonly fileSystemHelper: FileSystemHelper,
18 | ) {}
19 |
20 | async build(
21 | autoSchemaFile: string | boolean,
22 | options: FgqlModuleOptions,
23 | resolvers: Function[],
24 | ): Promise {
25 | const scalarsMap = this.scalarsExplorerService.getScalarsMap();
26 | try {
27 | const buildSchemaOptions = options.buildSchemaOptions || {};
28 | return await this.buildSchema(resolvers, autoSchemaFile, {
29 | ...buildSchemaOptions,
30 | scalarsMap,
31 | schemaDirectives: options.schemaDirectives,
32 | });
33 | } catch (err) {
34 | if (err && err.details) {
35 | console.error(err.details);
36 | }
37 | throw err;
38 | }
39 | }
40 |
41 | async buildFederatedSchema(
42 | autoSchemaFile: string | boolean,
43 | options: FgqlModuleOptions,
44 | resolvers: Function[],
45 | ) {
46 | const scalarsMap = this.scalarsExplorerService.getScalarsMap();
47 | try {
48 | const buildSchemaOptions = options.buildSchemaOptions || {};
49 | return await this.buildSchema(resolvers, autoSchemaFile, {
50 | ...buildSchemaOptions,
51 | directives: [
52 | ...specifiedDirectives,
53 | ...((buildSchemaOptions && buildSchemaOptions.directives) || []),
54 | ],
55 | scalarsMap,
56 | schemaDirectives: options.schemaDirectives,
57 | skipCheck: true,
58 | });
59 | } catch (err) {
60 | if (err && err.details) {
61 | console.error(err.details);
62 | }
63 | throw err;
64 | }
65 | }
66 |
67 | private async buildSchema(
68 | resolvers: Function[],
69 | autoSchemaFile: boolean | string,
70 | options: BuildSchemaOptions = {},
71 | ): Promise {
72 | const schema = await this.gqlSchemaFactory.create(resolvers, options);
73 | if (typeof autoSchemaFile !== 'boolean') {
74 | const filename = isString(autoSchemaFile)
75 | ? autoSchemaFile
76 | : resolve(process.cwd(), 'schema.gql');
77 |
78 | const fileContent = SDL_FILE_HEADER + printSchema(schema);
79 | await this.fileSystemHelper.writeFile(filename, fileContent);
80 | }
81 | return schema;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lib/graphql/graphql-schema.host.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { GraphQLSchema } from 'graphql';
3 |
4 | @Injectable()
5 | export class GraphQLSchemaHost {
6 | private _schema: GraphQLSchema;
7 |
8 | set schema(schemaRef: GraphQLSchema) {
9 | this._schema = schemaRef;
10 | }
11 |
12 | get schema(): GraphQLSchema {
13 | if (!this._schema) {
14 | throw new Error(
15 | 'GraphQL schema has not yet been created. ' +
16 | 'Make sure to call the "GraphQLSchemaHost#schema" getter when the application is already initialized (after the "onModuleInit" hook triggered by either "app.listen()" or "app.init()" method).',
17 | );
18 | }
19 | return this._schema;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/graphql/graphql-types.loader.ts:
--------------------------------------------------------------------------------
1 | import { mergeTypeDefs } from '@graphql-tools/merge';
2 | import { Injectable } from '@nestjs/common';
3 | import * as glob from 'fast-glob';
4 | import * as fs from 'fs';
5 | import { flatten } from 'lodash';
6 | import * as util from 'util';
7 |
8 | // eslint-disable-next-line @typescript-eslint/no-var-requires
9 | const normalize = require('normalize-path');
10 | const readFile = util.promisify(fs.readFile);
11 |
12 | @Injectable()
13 | export class GraphQLTypesLoader {
14 | async mergeTypesByPaths(paths: string | string[]): Promise {
15 | if (!paths || paths.length === 0) {
16 | return null;
17 | }
18 | const types = await this.getTypesFromPaths(paths);
19 | const flatTypes = flatten(types);
20 |
21 | return mergeTypeDefs(flatTypes, {
22 | throwOnConflict: true,
23 | commentDescriptions: true,
24 | reverseDirectives: true,
25 | });
26 | }
27 |
28 | private async getTypesFromPaths(paths: string | string[]): Promise {
29 | paths = util.isArray(paths)
30 | ? paths.map((path) => normalize(path))
31 | : normalize(paths);
32 |
33 | const filePaths = await glob(paths, {
34 | ignore: ['node_modules'],
35 | });
36 | if (filePaths.length === 0) {
37 | throw new Error(
38 | `No type definitions were found with the specified file name patterns: "${paths}". Please make sure there is at least one file that matches the given patterns.`,
39 | );
40 | }
41 | const fileContentsPromises = filePaths.sort().map((filePath) => {
42 | return readFile(filePath.toString(), 'utf8');
43 | });
44 |
45 | return Promise.all(fileContentsPromises);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/graphql/index.ts:
--------------------------------------------------------------------------------
1 | export * from './graphql-ast.explorer';
2 | export * from './graphql-schema.builder';
3 | export * from './graphql-schema.host';
4 | export * from './graphql-types.loader';
5 | export * from './graphql.factory';
6 |
--------------------------------------------------------------------------------
/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * from './decorators';
2 | export * from './fgql.module';
3 | export * from './graphql';
4 | export * from './interfaces';
5 | export * from './scalars';
6 | export * from './schema-builder';
7 | export * from './services/gql-arguments-host';
8 | export * from './services/gql-execution-context';
9 | export * from './type-factories';
10 |
--------------------------------------------------------------------------------
/lib/interfaces/base-type-options.interface.ts:
--------------------------------------------------------------------------------
1 | export type NullableList = 'items' | 'itemsAndList';
2 | export interface BaseTypeOptions {
3 | /**
4 | * Determines whether field/argument/etc is nullable.
5 | */
6 | nullable?: boolean | NullableList;
7 | /**
8 | * Default value.
9 | */
10 | defaultValue?: any;
11 | }
12 |
--------------------------------------------------------------------------------
/lib/interfaces/build-schema-options.interface.ts:
--------------------------------------------------------------------------------
1 | import { GraphQLDirective, GraphQLScalarType } from 'graphql';
2 |
3 | export type DateScalarMode = 'isoDate' | 'timestamp';
4 |
5 | export interface ScalarsTypeMap {
6 | type: Function;
7 | scalar: GraphQLScalarType;
8 | }
9 |
10 | export interface BuildSchemaOptions {
11 | /**
12 | * Date scalar mode
13 | */
14 | dateScalarMode?: DateScalarMode;
15 |
16 | /**
17 | * Scalars map
18 | */
19 | scalarsMap?: ScalarsTypeMap[];
20 |
21 | /**
22 | * Orphaned type classes that are not explicitly used in GraphQL types definitions
23 | */
24 | orphanedTypes?: Function[];
25 |
26 | /**
27 | * Disable checking on build the correctness of a schema
28 | */
29 | skipCheck?: boolean;
30 |
31 | /**
32 | * GraphQL directives
33 | */
34 | directives?: GraphQLDirective[];
35 |
36 | /**
37 | * GraphQL schema directives mapping
38 | */
39 | schemaDirectives?: Record;
40 | }
41 |
--------------------------------------------------------------------------------
/lib/interfaces/complexity.interface.ts:
--------------------------------------------------------------------------------
1 | import { GraphQLCompositeType, GraphQLField } from 'graphql';
2 |
3 | export type ComplexityEstimatorArgs = {
4 | type: GraphQLCompositeType;
5 | field: GraphQLField;
6 | args: { [key: string]: any };
7 | childComplexity: number;
8 | };
9 |
10 | export type ComplexityEstimator = (
11 | options: ComplexityEstimatorArgs,
12 | ) => number | void;
13 | export type Complexity = ComplexityEstimator | number;
14 |
--------------------------------------------------------------------------------
/lib/interfaces/custom-scalar.interface.ts:
--------------------------------------------------------------------------------
1 | import {
2 | GraphQLScalarLiteralParser,
3 | GraphQLScalarSerializer,
4 | GraphQLScalarValueParser,
5 | } from 'graphql';
6 |
7 | export interface CustomScalar {
8 | description?: string;
9 | parseValue: GraphQLScalarValueParser;
10 | serialize: GraphQLScalarSerializer;
11 | parseLiteral: GraphQLScalarLiteralParser;
12 | }
13 |
--------------------------------------------------------------------------------
/lib/interfaces/fgql-module-options.interface.ts:
--------------------------------------------------------------------------------
1 | import { ModuleMetadata, Type } from '@nestjs/common/interfaces';
2 | import * as GQL from 'fastify-gql';
3 | import { GraphQLSchema } from 'graphql';
4 | import { IResolvers } from 'graphql-tools';
5 | import { DefinitionsGeneratorOptions } from '../graphql/graphql-ast.explorer';
6 | import { BuildSchemaOptions } from './build-schema-options.interface';
7 |
8 | export interface FgqlModuleOptions
9 | extends Omit {
10 | schema?: GraphQLSchema;
11 | resolvers?: IResolvers;
12 | typeDefs?: string | string[];
13 | typePaths?: string[];
14 | transformSchema?: (
15 | schema: GraphQLSchema,
16 | ) => GraphQLSchema | Promise;
17 | definitions?: {
18 | path?: string;
19 | outputAs?: 'class' | 'interface';
20 | } & DefinitionsGeneratorOptions;
21 | autoSchemaFile?: string | boolean;
22 | buildSchemaOptions?: BuildSchemaOptions;
23 | include?: Function[];
24 | schemaDirectives?: Record;
25 | resolverValidationOptions?: IResolverValidationOptions;
26 | directiveResolvers?: any;
27 | /**
28 | * Enable/disable enhancers for @ResolveField()
29 | */
30 | fieldResolverEnhancers?: Enhancer[];
31 | }
32 |
33 | export type Enhancer = 'guards' | 'interceptors' | 'filters';
34 |
35 | export interface IResolverValidationOptions {
36 | requireResolversForArgs?: boolean;
37 | requireResolversForNonScalar?: boolean;
38 | requireResolversForAllFields?: boolean;
39 | requireResolversForResolveType?: boolean;
40 | allowResolversNotInSchema?: boolean;
41 | }
42 |
43 | export interface GqlOptionsFactory {
44 | createGqlOptions(): Promise | FgqlModuleOptions;
45 | }
46 |
47 | export interface GqlModuleAsyncOptions extends Pick {
48 | useExisting?: Type;
49 | useClass?: Type;
50 | useFactory?: (
51 | ...args: any[]
52 | ) => Promise | FgqlModuleOptions;
53 | inject?: any[];
54 | }
55 |
--------------------------------------------------------------------------------
/lib/interfaces/gql-exception-filter.interface.ts:
--------------------------------------------------------------------------------
1 | import { ArgumentsHost } from '@nestjs/common';
2 |
3 | export interface GqlExceptionFilter {
4 | catch(exception: TInput, host: ArgumentsHost): TOutput;
5 | }
6 |
--------------------------------------------------------------------------------
/lib/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from './base-type-options.interface';
2 | export * from './build-schema-options.interface';
3 | export * from './complexity.interface';
4 | export * from './custom-scalar.interface';
5 | export * from './fgql-module-options.interface';
6 | export * from './gql-exception-filter.interface';
7 | export * from './resolve-type-fn.interface';
8 | export * from './return-type-func.interface';
9 |
--------------------------------------------------------------------------------
/lib/interfaces/resolve-type-fn.interface.ts:
--------------------------------------------------------------------------------
1 | import { GraphQLTypeResolver } from 'graphql';
2 |
3 | export type ResolveTypeFn = (
4 | ...args: Parameters>
5 | ) => any;
6 |
--------------------------------------------------------------------------------
/lib/interfaces/resolver-metadata.interface.ts:
--------------------------------------------------------------------------------
1 | export interface ResolverMetadata {
2 | name: string;
3 | type: string;
4 | methodName: string;
5 | callback?: Function | Record;
6 | }
7 |
--------------------------------------------------------------------------------
/lib/interfaces/return-type-func.interface.ts:
--------------------------------------------------------------------------------
1 | import { Type } from '@nestjs/common';
2 | import { GraphQLScalarType } from 'graphql';
3 |
4 | export type GqlTypeReference =
5 | | Type
6 | | GraphQLScalarType
7 | | Function
8 | | object
9 | | symbol;
10 | export type ReturnTypeFuncValue = GqlTypeReference | [GqlTypeReference];
11 | export type ReturnTypeFunc = (returns?: void) => ReturnTypeFuncValue;
12 |
--------------------------------------------------------------------------------
/lib/interfaces/type-options.interface.ts:
--------------------------------------------------------------------------------
1 | import { BaseTypeOptions } from './base-type-options.interface';
2 |
3 | export interface TypeOptions extends BaseTypeOptions {
4 | isArray?: boolean;
5 | arrayDepth?: number;
6 | }
7 |
--------------------------------------------------------------------------------
/lib/plugin/compiler-plugin.ts:
--------------------------------------------------------------------------------
1 | import * as ts from 'typescript';
2 | import { mergePluginOptions } from './merge-options';
3 | import { ModelClassVisitor } from './visitors/model-class.visitor';
4 |
5 | const typeClassVisitor = new ModelClassVisitor();
6 | const isFilenameMatched = (patterns: string[], filename: string) =>
7 | patterns.some(path => filename.includes(path));
8 |
9 | export const before = (options?: Record, program?: ts.Program) => {
10 | options = mergePluginOptions(options);
11 |
12 | return (ctx: ts.TransformationContext): ts.Transformer => {
13 | return (sf: ts.SourceFile) => {
14 | if (isFilenameMatched(options.typeFileNameSuffix, sf.fileName)) {
15 | return typeClassVisitor.visit(sf, ctx, program);
16 | }
17 | return sf;
18 | };
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/lib/plugin/index.ts:
--------------------------------------------------------------------------------
1 | export * from './compiler-plugin';
2 |
--------------------------------------------------------------------------------
/lib/plugin/merge-options.ts:
--------------------------------------------------------------------------------
1 | import { isString } from '@nestjs/common/utils/shared.utils';
2 |
3 | export interface PluginOptions {
4 | typeFileNameSuffix?: string | string[];
5 | }
6 |
7 | const defaultOptions: PluginOptions = {
8 | typeFileNameSuffix: ['.input.ts', '.args.ts', '.entity.ts', '.model.ts'],
9 | };
10 |
11 | export const mergePluginOptions = (
12 | options: Record = {},
13 | ): PluginOptions => {
14 | if (isString(options.typeFileNameSuffix)) {
15 | options.typeFileNameSuffix = [options.typeFileNameSuffix];
16 | }
17 | return {
18 | ...defaultOptions,
19 | ...options,
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/lib/plugin/plugin-constants.ts:
--------------------------------------------------------------------------------
1 | export const GRAPHQL_PACKAGE_NAMESPACE = 'graphql';
2 | export const GRAPHQL_PACKAGE_NAME = '@nestjs/graphql';
3 | export const METADATA_FACTORY_NAME = '_GRAPHQL_METADATA_FACTORY';
4 |
--------------------------------------------------------------------------------
/lib/plugin/utils/ast-utils.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CallExpression,
3 | Decorator,
4 | Identifier,
5 | LeftHandSideExpression,
6 | Node,
7 | ObjectFlags,
8 | ObjectType,
9 | PropertyAccessExpression,
10 | SyntaxKind,
11 | Type,
12 | TypeChecker,
13 | TypeFlags,
14 | TypeFormatFlags,
15 | } from 'typescript';
16 | import { isDynamicallyAdded } from './plugin-utils';
17 |
18 | export function isArray(type: Type) {
19 | const symbol = type.getSymbol();
20 | if (!symbol) {
21 | return false;
22 | }
23 | return symbol.getName() === 'Array' && getTypeArguments(type).length === 1;
24 | }
25 |
26 | export function getTypeArguments(type: Type) {
27 | return (type as any).typeArguments || [];
28 | }
29 |
30 | export function isBoolean(type: Type) {
31 | return hasFlag(type, TypeFlags.Boolean);
32 | }
33 |
34 | export function isString(type: Type) {
35 | return hasFlag(type, TypeFlags.String);
36 | }
37 |
38 | export function isNumber(type: Type) {
39 | return hasFlag(type, TypeFlags.Number);
40 | }
41 |
42 | export function isInterface(type: Type) {
43 | return hasObjectFlag(type, ObjectFlags.Interface);
44 | }
45 |
46 | export function isEnum(type: Type) {
47 | const hasEnumFlag = hasFlag(type, TypeFlags.Enum);
48 | if (hasEnumFlag) {
49 | return true;
50 | }
51 | if (isEnumLiteral(type)) {
52 | return false;
53 | }
54 | const symbol = type.getSymbol();
55 | if (!symbol) {
56 | return false;
57 | }
58 | const valueDeclaration = symbol.valueDeclaration;
59 | if (!valueDeclaration) {
60 | return false;
61 | }
62 | return valueDeclaration.kind === SyntaxKind.EnumDeclaration;
63 | }
64 |
65 | export function isEnumLiteral(type: Type) {
66 | return hasFlag(type, TypeFlags.EnumLiteral) && !type.isUnion();
67 | }
68 |
69 | export function hasFlag(type: Type, flag: TypeFlags) {
70 | return (type.flags & flag) === flag;
71 | }
72 |
73 | export function hasObjectFlag(type: Type, flag: ObjectFlags) {
74 | return ((type as ObjectType).objectFlags & flag) === flag;
75 | }
76 |
77 | export function getText(
78 | type: Type,
79 | typeChecker: TypeChecker,
80 | enclosingNode?: Node,
81 | typeFormatFlags?: TypeFormatFlags,
82 | ) {
83 | if (!typeFormatFlags) {
84 | typeFormatFlags = getDefaultTypeFormatFlags(enclosingNode);
85 | }
86 | const compilerNode = !enclosingNode ? undefined : enclosingNode;
87 | return typeChecker.typeToString(type, compilerNode, typeFormatFlags);
88 | }
89 |
90 | export function getDefaultTypeFormatFlags(enclosingNode: Node) {
91 | let formatFlags =
92 | TypeFormatFlags.UseTypeOfFunction |
93 | TypeFormatFlags.NoTruncation |
94 | TypeFormatFlags.UseFullyQualifiedType |
95 | TypeFormatFlags.WriteTypeArgumentsOfSignature;
96 | if (enclosingNode && enclosingNode.kind === SyntaxKind.TypeAliasDeclaration)
97 | formatFlags |= TypeFormatFlags.InTypeAlias;
98 | return formatFlags;
99 | }
100 |
101 | export function getDecoratorArguments(decorator: Decorator) {
102 | const callExpression = decorator.expression;
103 | return (callExpression && (callExpression as CallExpression).arguments) || [];
104 | }
105 |
106 | export function getDecoratorName(decorator: Decorator) {
107 | const isDecoratorFactory =
108 | decorator.expression.kind === SyntaxKind.CallExpression;
109 | if (isDecoratorFactory) {
110 | const callExpression = decorator.expression;
111 | const identifier = (callExpression as CallExpression)
112 | .expression as Identifier;
113 | if (isDynamicallyAdded(identifier)) {
114 | return undefined;
115 | }
116 | return getIdentifierFromName(
117 | (callExpression as CallExpression).expression,
118 | ).getText();
119 | }
120 | return getIdentifierFromName(decorator.expression).getText();
121 | }
122 |
123 | function getIdentifierFromName(expression: LeftHandSideExpression) {
124 | const identifier = getNameFromExpression(expression);
125 | if (expression && expression.kind !== SyntaxKind.Identifier) {
126 | throw new Error();
127 | }
128 | return identifier;
129 | }
130 |
131 | function getNameFromExpression(expression: LeftHandSideExpression) {
132 | if (expression && expression.kind === SyntaxKind.PropertyAccessExpression) {
133 | return (expression as PropertyAccessExpression).name;
134 | }
135 | return expression;
136 | }
137 |
--------------------------------------------------------------------------------
/lib/scalars/index.ts:
--------------------------------------------------------------------------------
1 | import { GraphQLFloat, GraphQLID, GraphQLInt } from 'graphql';
2 |
3 | export * from './iso-date.scalar';
4 | export * from './timestamp.scalar';
5 |
6 | export const Int = GraphQLInt;
7 | export const Float = GraphQLFloat;
8 | export const ID = GraphQLID;
9 |
--------------------------------------------------------------------------------
/lib/scalars/iso-date.scalar.ts:
--------------------------------------------------------------------------------
1 | import { GraphQLScalarType, Kind, ValueNode } from 'graphql';
2 |
3 | export const GraphQLISODateTime = new GraphQLScalarType({
4 | name: 'DateTime',
5 | description:
6 | 'A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format.',
7 | parseValue(value: string) {
8 | return new Date(value);
9 | },
10 | serialize(value: Date) {
11 | return value instanceof Date ? value.toISOString() : null;
12 | },
13 | parseLiteral(ast: ValueNode) {
14 | return ast.kind === Kind.STRING ? new Date(ast.value) : null;
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/lib/scalars/timestamp.scalar.ts:
--------------------------------------------------------------------------------
1 | import { GraphQLScalarType, Kind, ValueNode } from 'graphql';
2 |
3 | export const GraphQLTimestamp = new GraphQLScalarType({
4 | name: 'Timestamp',
5 | description:
6 | '`Date` type as integer. Type represents date and time as number of milliseconds from start of UNIX epoch.',
7 | serialize(value: Date) {
8 | return value instanceof Date ? value.getTime() : null;
9 | },
10 | parseValue(value: string | null) {
11 | try {
12 | return value !== null ? new Date(value) : null;
13 | } catch {
14 | return null;
15 | }
16 | },
17 | parseLiteral(ast: ValueNode) {
18 | if (ast.kind === Kind.INT) {
19 | const num = parseInt(ast.value, 10);
20 | return new Date(num);
21 | } else if (ast.kind === Kind.STRING) {
22 | return this.parseValue(ast.value);
23 | }
24 | return null;
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/cannot-determine-input-type.error.ts:
--------------------------------------------------------------------------------
1 | export class CannotDetermineInputTypeError extends Error {
2 | constructor(hostType: string) {
3 | super(
4 | `Cannot determine a GraphQL input type for the "${hostType}". Make sure your class is decorated with an appropriate decorator.`,
5 | );
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/cannot-determine-output-type.error.ts:
--------------------------------------------------------------------------------
1 | export class CannotDetermineOutputTypeError extends Error {
2 | constructor(hostType: string) {
3 | super(
4 | `Cannot determine a GraphQL output type for the "${hostType}". Make sure your class is decorated with an appropriate decorator.`,
5 | );
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/default-nullable-conflict.error.ts:
--------------------------------------------------------------------------------
1 | import { NullableList } from '../../interfaces';
2 |
3 | export class DefaultNullableConflictError extends Error {
4 | constructor(
5 | hostTypeName: string,
6 | defaultVal: any,
7 | isNullable: boolean | NullableList,
8 | ) {
9 | super(
10 | `Incorrect "nullable" option value set for ${hostTypeName}. Do not combine "defaultValue: ${defaultVal}" with "nullable: ${isNullable}".`,
11 | );
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/default-values-conflict.error.ts:
--------------------------------------------------------------------------------
1 | export class DefaultValuesConflictError extends Error {
2 | constructor(
3 | hostTypeName: string,
4 | fieldName: string,
5 | decoratorDefaultVal: unknown,
6 | initializerDefaultVal: unknown,
7 | ) {
8 | super(
9 | `Error caused by mis-matched default values for the "${fieldName}" field of "${hostTypeName}". The default value from the decorator "${decoratorDefaultVal}" is not equal to the property initializer value "${initializerDefaultVal}". Ensure that these values match.`,
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/directive-parsing.error.ts:
--------------------------------------------------------------------------------
1 | export class DirectiveParsingError extends Error {
2 | constructor(sdl: string) {
3 | super(
4 | `Directive SDL "${sdl}" is invalid. Please, pass a valid directive definition.`,
5 | );
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/invalid-nullable-option.error.ts:
--------------------------------------------------------------------------------
1 | import { NullableList } from '../../interfaces';
2 |
3 | export class InvalidNullableOptionError extends Error {
4 | constructor(name: string, nullable?: boolean | NullableList) {
5 | super(
6 | `Incorrect nullable option set for ${name}. Do not combine non-list type with nullable "${nullable}".`,
7 | );
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/return-type-cannot-be-resolved.error.ts:
--------------------------------------------------------------------------------
1 | export class ReturnTypeCannotBeResolvedError extends Error {
2 | constructor(hostTypeName: string) {
3 | super(
4 | `Return type for "${hostTypeName}" cannot be resolved. If you did not pass a custom implementation (the "resolveType" function), you must return an instance of a class instead of a plain JavaScript object.`,
5 | );
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/schema-generation.error.ts:
--------------------------------------------------------------------------------
1 | import { GraphQLError } from 'graphql';
2 |
3 | export class SchemaGenerationError extends Error {
4 | constructor(public readonly details: ReadonlyArray) {
5 | super('Schema generation error (code-first approach)');
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/unable-to-find-fields.error.ts:
--------------------------------------------------------------------------------
1 | export class UnableToFindFieldsError extends Error {
2 | constructor(name: string) {
3 | super(
4 | `Unable to find fields for GraphQL type ${name}. Is your class annotated with an appropriate decorator?`,
5 | );
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/undefined-resolver-type.error.ts:
--------------------------------------------------------------------------------
1 | export class UndefinedResolverTypeError extends Error {
2 | constructor(name: string) {
3 | super(
4 | `Undefined resolver type error. Make sure you are providing an explicit object type for the "${name}" (e.g., "@Resolver(() => Cat)").`,
5 | );
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/undefined-return-type.error.ts:
--------------------------------------------------------------------------------
1 | export class UndefinedReturnTypeError extends Error {
2 | constructor(decoratorName: string, methodKey: string) {
3 | super(
4 | `"${decoratorName}.${methodKey}" was defined in resolvers, but not in schema. If you use the @${decoratorName}() decorator with the code first approach enabled, remember to explicitly provide a return type function, e.g. @${decoratorName}(returns => Author).`,
5 | );
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/schema-builder/errors/undefined-type.error.ts:
--------------------------------------------------------------------------------
1 | import { isUndefined } from '@nestjs/common/utils/shared.utils';
2 |
3 | export class UndefinedTypeError extends Error {
4 | constructor(name: string, key: string, index?: number) {
5 | super(
6 | `Undefined type error. Make sure you are providing an explicit type for the "${key}" ${
7 | isUndefined(index) ? '' : `(parameter at index [${index}]) `
8 | }of the "${name}" class.`,
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/args.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { isUndefined } from '@nestjs/common/utils/shared.utils';
3 | import { GraphQLFieldConfigArgumentMap } from 'graphql';
4 | import { BuildSchemaOptions } from '../../interfaces';
5 | import { getDefaultValue } from '../helpers/get-default-value.helper';
6 | import { ClassMetadata, MethodArgsMetadata } from '../metadata';
7 | import { TypeMetadataStorage } from '../storages/type-metadata.storage';
8 | import { InputTypeFactory } from './input-type.factory';
9 |
10 | @Injectable()
11 | export class ArgsFactory {
12 | constructor(private readonly inputTypeFactory: InputTypeFactory) {}
13 |
14 | public create(
15 | args: MethodArgsMetadata[],
16 | options: BuildSchemaOptions,
17 | ): GraphQLFieldConfigArgumentMap {
18 | const fieldConfigMap: GraphQLFieldConfigArgumentMap = {};
19 | args.forEach(param => {
20 | if (param.kind === 'arg') {
21 | fieldConfigMap[param.name] = {
22 | description: param.description,
23 | type: this.inputTypeFactory.create(
24 | param.name,
25 | param.typeFn(),
26 | options,
27 | param.options,
28 | ),
29 | defaultValue: param.options.defaultValue,
30 | };
31 | } else if (param.kind === 'args') {
32 | const argumentTypes = TypeMetadataStorage.getArgumentsMetadata();
33 | const hostType = param.typeFn();
34 | const argumentType = argumentTypes.find(
35 | item => item.target === hostType,
36 | )!;
37 |
38 | let parent = Object.getPrototypeOf(argumentType.target);
39 | while (!isUndefined(parent.prototype)) {
40 | const parentArgType = argumentTypes.find(
41 | item => item.target === parent,
42 | );
43 | if (parentArgType) {
44 | this.inheritParentArgs(parentArgType, options, fieldConfigMap);
45 | }
46 | parent = Object.getPrototypeOf(parent);
47 | }
48 | this.inheritParentArgs(argumentType, options, fieldConfigMap);
49 | }
50 | });
51 | return fieldConfigMap;
52 | }
53 |
54 | private inheritParentArgs(
55 | argType: ClassMetadata,
56 | options: BuildSchemaOptions,
57 | fieldConfigMap: GraphQLFieldConfigArgumentMap = {},
58 | ) {
59 | const argumentInstance = new (argType.target as any)();
60 | argType.properties.forEach(field => {
61 | field.options.defaultValue = getDefaultValue(
62 | argumentInstance,
63 | field.options,
64 | field.name,
65 | argType.name,
66 | );
67 |
68 | const { schemaName } = field;
69 | fieldConfigMap[schemaName] = {
70 | description: field.description,
71 | type: this.inputTypeFactory.create(
72 | field.name,
73 | field.typeFn(),
74 | options,
75 | field.options,
76 | ),
77 | defaultValue: field.options.defaultValue,
78 | };
79 | });
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/ast-definition-node.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { isEmpty } from '@nestjs/common/utils/shared.utils';
3 | import {
4 | DirectiveNode,
5 | FieldDefinitionNode,
6 | GraphQLInputType,
7 | GraphQLOutputType,
8 | InputObjectTypeDefinitionNode,
9 | InputValueDefinitionNode,
10 | ObjectTypeDefinitionNode,
11 | parse,
12 | } from 'graphql';
13 | import { head } from 'lodash';
14 | import { DirectiveParsingError } from '../errors/directive-parsing.error';
15 | import { DirectiveMetadata } from '../metadata/directive.metadata';
16 |
17 | @Injectable()
18 | export class AstDefinitionNodeFactory {
19 | /**
20 | * The implementation of this class has been heavily inspired by the folllowing code:
21 | * @ref https://github.com/MichalLytek/type-graphql/blob/master/src/schema/definition-node.ts
22 | * implemented in this PR https://github.com/MichalLytek/type-graphql/pull/369 by Jordan Stous (https://github.com/j)
23 | */
24 |
25 | createObjectTypeNode(
26 | name: string,
27 | directiveMetadata?: DirectiveMetadata[],
28 | ): ObjectTypeDefinitionNode | undefined {
29 | if (isEmpty(directiveMetadata)) {
30 | return;
31 | }
32 | return {
33 | kind: 'ObjectTypeDefinition',
34 | name: {
35 | kind: 'Name',
36 | value: name,
37 | },
38 | directives: directiveMetadata.map(this.createDirectiveNode),
39 | };
40 | }
41 |
42 | createInputObjectTypeNode(
43 | name: string,
44 | directiveMetadata?: DirectiveMetadata[],
45 | ): InputObjectTypeDefinitionNode | undefined {
46 | if (isEmpty(directiveMetadata)) {
47 | return;
48 | }
49 | return {
50 | kind: 'InputObjectTypeDefinition',
51 | name: {
52 | kind: 'Name',
53 | value: name,
54 | },
55 | directives: directiveMetadata.map(this.createDirectiveNode),
56 | };
57 | }
58 |
59 | createFieldNode(
60 | name: string,
61 | type: GraphQLOutputType,
62 | directiveMetadata?: DirectiveMetadata[],
63 | ): FieldDefinitionNode | undefined {
64 | if (isEmpty(directiveMetadata)) {
65 | return;
66 | }
67 | return {
68 | kind: 'FieldDefinition',
69 | type: {
70 | kind: 'NamedType',
71 | name: {
72 | kind: 'Name',
73 | value: type.toString(),
74 | },
75 | },
76 | name: {
77 | kind: 'Name',
78 | value: name,
79 | },
80 | directives: directiveMetadata.map(this.createDirectiveNode),
81 | };
82 | }
83 |
84 | createInputValueNode(
85 | name: string,
86 | type: GraphQLInputType,
87 | directiveMetadata?: DirectiveMetadata[],
88 | ): InputValueDefinitionNode | undefined {
89 | if (isEmpty(directiveMetadata)) {
90 | return;
91 | }
92 | return {
93 | kind: 'InputValueDefinition',
94 | type: {
95 | kind: 'NamedType',
96 | name: {
97 | kind: 'Name',
98 | value: type.toString(),
99 | },
100 | },
101 | name: {
102 | kind: 'Name',
103 | value: name,
104 | },
105 | directives: directiveMetadata.map(this.createDirectiveNode),
106 | };
107 | }
108 |
109 | private createDirectiveNode(directive: DirectiveMetadata): DirectiveNode {
110 | const parsed = parse(`type String ${directive.sdl}`);
111 | const definitions = parsed.definitions as ObjectTypeDefinitionNode[];
112 | const directives = definitions
113 | .filter(item => item.directives && item.directives.length > 0)
114 | .map(({ directives }) => directives)
115 | .reduce((acc, item) => [...acc, ...item]);
116 |
117 | if (directives.length !== 1) {
118 | throw new DirectiveParsingError(directive.sdl);
119 | }
120 | return head(directives);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/enum-definition.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { GraphQLEnumType } from 'graphql';
3 | import { EnumMetadata } from '../metadata';
4 |
5 | export interface EnumDefinition {
6 | enumRef: object;
7 | type: GraphQLEnumType;
8 | }
9 |
10 | @Injectable()
11 | export class EnumDefinitionFactory {
12 | public create(metadata: EnumMetadata): EnumDefinition {
13 | const enumValues = this.getEnumValues(metadata.ref);
14 |
15 | return {
16 | enumRef: metadata.ref,
17 | type: new GraphQLEnumType({
18 | name: metadata.name,
19 | description: metadata.description,
20 | values: Object.keys(enumValues).reduce((prevValue, key) => {
21 | prevValue[key] = {
22 | value: enumValues[key],
23 | };
24 | return prevValue;
25 | }, {}),
26 | }),
27 | };
28 | }
29 |
30 | private getEnumValues(enumObject: Record) {
31 | const enumKeys = Object.keys(enumObject).filter(key =>
32 | isNaN(parseInt(key, 10)),
33 | );
34 | return enumKeys.reduce((prev, nextKey) => {
35 | prev[nextKey] = enumObject[nextKey];
36 | return prev;
37 | }, {});
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/factories.ts:
--------------------------------------------------------------------------------
1 | import { ArgsFactory } from './args.factory';
2 | import { AstDefinitionNodeFactory } from './ast-definition-node.factory';
3 | import { EnumDefinitionFactory } from './enum-definition.factory';
4 | import { InputTypeDefinitionFactory } from './input-type-definition.factory';
5 | import { InputTypeFactory } from './input-type.factory';
6 | import { InterfaceDefinitionFactory } from './interface-definition.factory';
7 | import { MutationTypeFactory } from './mutation-type.factory';
8 | import { ObjectTypeDefinitionFactory } from './object-type-definition.factory';
9 | import { OrphanedTypesFactory } from './orphaned-types.factory';
10 | import { OutputTypeFactory } from './output-type.factory';
11 | import { QueryTypeFactory } from './query-type.factory';
12 | import { ResolveTypeFactory } from './resolve-type.factory';
13 | import { RootTypeFactory } from './root-type.factory';
14 | import { SubscriptionTypeFactory } from './subscription-type.factory';
15 | import { UnionDefinitionFactory } from './union-definition.factory';
16 |
17 | export const schemaBuilderFactories = [
18 | EnumDefinitionFactory,
19 | InputTypeDefinitionFactory,
20 | ArgsFactory,
21 | InputTypeFactory,
22 | InterfaceDefinitionFactory,
23 | MutationTypeFactory,
24 | ObjectTypeDefinitionFactory,
25 | OutputTypeFactory,
26 | OrphanedTypesFactory,
27 | OutputTypeFactory,
28 | QueryTypeFactory,
29 | ResolveTypeFactory,
30 | RootTypeFactory,
31 | SubscriptionTypeFactory,
32 | UnionDefinitionFactory,
33 | AstDefinitionNodeFactory,
34 | ];
35 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/input-type-definition.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Type } from '@nestjs/common';
2 | import { isUndefined } from '@nestjs/common/utils/shared.utils';
3 | import { GraphQLInputFieldConfigMap, GraphQLInputObjectType } from 'graphql';
4 | import { BuildSchemaOptions } from '../../interfaces';
5 | import { getDefaultValue } from '../helpers/get-default-value.helper';
6 | import { ClassMetadata } from '../metadata';
7 | import { TypeFieldsAccessor } from '../services/type-fields.accessor';
8 | import { TypeDefinitionsStorage } from '../storages/type-definitions.storage';
9 | import { AstDefinitionNodeFactory } from './ast-definition-node.factory';
10 | import { InputTypeFactory } from './input-type.factory';
11 |
12 | export interface InputTypeDefinition {
13 | target: Function;
14 | type: GraphQLInputObjectType;
15 | isAbstract: boolean;
16 | }
17 |
18 | @Injectable()
19 | export class InputTypeDefinitionFactory {
20 | constructor(
21 | private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
22 | private readonly inputTypeFactory: InputTypeFactory,
23 | private readonly typeFieldsAccessor: TypeFieldsAccessor,
24 | private readonly astDefinitionNodeFactory: AstDefinitionNodeFactory,
25 | ) {}
26 |
27 | public create(
28 | metadata: ClassMetadata,
29 | options: BuildSchemaOptions,
30 | ): InputTypeDefinition {
31 | return {
32 | target: metadata.target,
33 | isAbstract: metadata.isAbstract || false,
34 | type: new GraphQLInputObjectType({
35 | name: metadata.name,
36 | description: metadata.description,
37 | fields: this.generateFields(metadata, options),
38 | /**
39 | * AST node has to be manually created in order to define directives
40 | * (more on this topic here: https://github.com/graphql/graphql-js/issues/1343)
41 | */
42 | astNode: this.astDefinitionNodeFactory.createInputObjectTypeNode(
43 | metadata.name,
44 | metadata.directives,
45 | ),
46 | extensions: metadata.extensions,
47 | }),
48 | };
49 | }
50 |
51 | private generateFields(
52 | metadata: ClassMetadata,
53 | options: BuildSchemaOptions,
54 | ): () => GraphQLInputFieldConfigMap {
55 | const instance = new (metadata.target as Type)();
56 | const prototype = Object.getPrototypeOf(metadata.target);
57 |
58 | const getParentType = () => {
59 | const parentTypeDefinition = this.typeDefinitionsStorage.getInputTypeByTarget(
60 | prototype,
61 | );
62 | return parentTypeDefinition ? parentTypeDefinition.type : undefined;
63 | };
64 | return () => {
65 | let fields: GraphQLInputFieldConfigMap = {};
66 | metadata.properties.forEach((property) => {
67 | property.options.defaultValue = getDefaultValue(
68 | instance,
69 | property.options,
70 | property.name,
71 | metadata.name,
72 | );
73 |
74 | const type = this.inputTypeFactory.create(
75 | property.name,
76 | property.typeFn(),
77 | options,
78 | property.options,
79 | );
80 | fields[property.schemaName] = {
81 | description: property.description,
82 | type,
83 | defaultValue: property.options.defaultValue,
84 | /**
85 | * AST node has to be manually created in order to define directives
86 | * (more on this topic here: https://github.com/graphql/graphql-js/issues/1343)
87 | */
88 | astNode: this.astDefinitionNodeFactory.createInputValueNode(
89 | property.name,
90 | type,
91 | property.directives,
92 | ),
93 | extensions: metadata.extensions,
94 | };
95 | });
96 |
97 | if (!isUndefined(prototype.prototype)) {
98 | const parentClassRef = getParentType();
99 | if (parentClassRef) {
100 | const parentFields = this.typeFieldsAccessor.extractFromInputType(
101 | parentClassRef,
102 | );
103 | fields = {
104 | ...parentFields,
105 | ...fields,
106 | };
107 | }
108 | }
109 | return fields;
110 | };
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/input-type.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { GraphQLInputType } from 'graphql';
3 | import { BuildSchemaOptions, GqlTypeReference } from '../../interfaces';
4 | import { TypeOptions } from '../../interfaces/type-options.interface';
5 | import { CannotDetermineInputTypeError } from '../errors/cannot-determine-input-type.error';
6 | import { TypeMapperSevice } from '../services/type-mapper.service';
7 | import { TypeDefinitionsStorage } from '../storages/type-definitions.storage';
8 |
9 | @Injectable()
10 | export class InputTypeFactory {
11 | constructor(
12 | private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
13 | private readonly typeMapperService: TypeMapperSevice,
14 | ) {}
15 |
16 | public create(
17 | hostType: string,
18 | typeRef: GqlTypeReference,
19 | buildOptions: BuildSchemaOptions,
20 | typeOptions: TypeOptions = {},
21 | ): GraphQLInputType {
22 | let inputType:
23 | | GraphQLInputType
24 | | undefined = this.typeMapperService.mapToScalarType(
25 | typeRef,
26 | buildOptions.scalarsMap,
27 | buildOptions.dateScalarMode,
28 | );
29 | if (!inputType) {
30 | inputType = this.typeDefinitionsStorage.getInputTypeAndExtract(
31 | typeRef as any,
32 | );
33 | if (!inputType) {
34 | throw new CannotDetermineInputTypeError(hostType);
35 | }
36 | }
37 | return this.typeMapperService.mapToGqlType(
38 | hostType,
39 | inputType,
40 | typeOptions,
41 | true,
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/interface-definition.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { isUndefined } from '@nestjs/common/utils/shared.utils';
3 | import { GraphQLFieldConfigMap, GraphQLInterfaceType } from 'graphql';
4 | import { BuildSchemaOptions } from '../../interfaces';
5 | import { ReturnTypeCannotBeResolvedError } from '../errors/return-type-cannot-be-resolved.error';
6 | import { InterfaceMetadata } from '../metadata/interface.metadata';
7 | import { TypeFieldsAccessor } from '../services/type-fields.accessor';
8 | import { TypeDefinitionsStorage } from '../storages/type-definitions.storage';
9 | import { TypeMetadataStorage } from '../storages/type-metadata.storage';
10 | import { OutputTypeFactory } from './output-type.factory';
11 | import { ResolveTypeFactory } from './resolve-type.factory';
12 |
13 | export interface InterfaceTypeDefinition {
14 | target: Function;
15 | type: GraphQLInterfaceType;
16 | isAbstract: boolean;
17 | }
18 |
19 | @Injectable()
20 | export class InterfaceDefinitionFactory {
21 | constructor(
22 | private readonly resolveTypeFactory: ResolveTypeFactory,
23 | private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
24 | private readonly outputTypeFactory: OutputTypeFactory,
25 | private readonly typeFieldsAccessor: TypeFieldsAccessor,
26 | ) {}
27 |
28 | public create(
29 | metadata: InterfaceMetadata,
30 | options: BuildSchemaOptions,
31 | ): InterfaceTypeDefinition {
32 | const resolveType = this.createResolveTypeFn(metadata);
33 | return {
34 | target: metadata.target,
35 | isAbstract: metadata.isAbstract || false,
36 | type: new GraphQLInterfaceType({
37 | name: metadata.name,
38 | description: metadata.description,
39 | fields: this.generateFields(metadata, options),
40 | resolveType,
41 | }),
42 | };
43 | }
44 |
45 | private createResolveTypeFn(metadata: InterfaceMetadata) {
46 | const objectTypesMetadata = TypeMetadataStorage.getObjectTypesMetadata();
47 | const implementedTypes = objectTypesMetadata
48 | .filter(
49 | objectType =>
50 | objectType.interfaces &&
51 | objectType.interfaces.includes(metadata.target),
52 | )
53 | .map(objectType => objectType.target);
54 |
55 | return metadata.resolveType
56 | ? this.resolveTypeFactory.getResolveTypeFunction(metadata.resolveType)
57 | : (instance: any) => {
58 | const target = implementedTypes.find(
59 | Type => instance instanceof Type,
60 | );
61 | if (!target) {
62 | throw new ReturnTypeCannotBeResolvedError(metadata.name);
63 | }
64 | return this.typeDefinitionsStorage.getObjectTypeByTarget(target).type;
65 | };
66 | }
67 |
68 | private generateFields(
69 | metadata: InterfaceMetadata,
70 | options: BuildSchemaOptions,
71 | ): () => GraphQLFieldConfigMap {
72 | const prototype = Object.getPrototypeOf(metadata.target);
73 | const getParentType = () => {
74 | const parentTypeDefinition = this.typeDefinitionsStorage.getInterfaceByTarget(
75 | prototype,
76 | );
77 | return parentTypeDefinition ? parentTypeDefinition.type : undefined;
78 | };
79 |
80 | return () => {
81 | let fields: GraphQLFieldConfigMap = {};
82 | metadata.properties.forEach(property => {
83 | fields[property.schemaName] = {
84 | description: property.description,
85 | type: this.outputTypeFactory.create(
86 | property.name,
87 | property.typeFn(),
88 | options,
89 | property.options,
90 | ),
91 | };
92 | });
93 |
94 | if (!isUndefined(prototype.prototype)) {
95 | const parentClassRef = getParentType();
96 | if (parentClassRef) {
97 | const parentFields = this.typeFieldsAccessor.extractFromInterfaceOrObjectType(
98 | parentClassRef,
99 | );
100 | fields = {
101 | ...parentFields,
102 | ...fields,
103 | };
104 | }
105 | }
106 | return fields;
107 | };
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/mutation-type.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { GraphQLObjectType } from 'graphql';
3 | import { BuildSchemaOptions } from '../../interfaces';
4 | import { TypeMetadataStorage } from '../storages/type-metadata.storage';
5 | import { RootTypeFactory } from './root-type.factory';
6 |
7 | @Injectable()
8 | export class MutationTypeFactory {
9 | constructor(private readonly rootTypeFactory: RootTypeFactory) {}
10 |
11 | public create(
12 | typeRefs: Function[],
13 | options: BuildSchemaOptions,
14 | ): GraphQLObjectType {
15 | const objectTypeName = 'Mutation';
16 | const mutationsMetadata = TypeMetadataStorage.getMutationsMetadata();
17 |
18 | return this.rootTypeFactory.create(
19 | typeRefs,
20 | mutationsMetadata,
21 | objectTypeName,
22 | options,
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/orphaned-types.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { GraphQLNamedType } from 'graphql';
3 | import { OrphanedReferenceRegistry } from '../services/orphaned-reference.registry';
4 | import { TypeDefinitionsStorage } from '../storages/type-definitions.storage';
5 | import { ObjectTypeDefinition } from './object-type-definition.factory';
6 |
7 | @Injectable()
8 | export class OrphanedTypesFactory {
9 | constructor(
10 | private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
11 | private readonly orphanedReferenceRegistry: OrphanedReferenceRegistry,
12 | ) {}
13 |
14 | public create(types: Function[]): GraphQLNamedType[] {
15 | types = (types || []).concat(this.orphanedReferenceRegistry.getAll());
16 |
17 | if (types.length === 0) {
18 | return [];
19 | }
20 | const interfaceTypeDefs = this.typeDefinitionsStorage.getAllInterfaceDefinitions();
21 | const objectTypeDefs = this.typeDefinitionsStorage.getAllObjectTypeDefinitions();
22 | const inputTypeDefs = this.typeDefinitionsStorage.getAllInputTypeDefinitions();
23 | const classTypeDefs = [
24 | ...interfaceTypeDefs,
25 | ...objectTypeDefs,
26 | ...inputTypeDefs,
27 | ];
28 | return classTypeDefs
29 | .filter(item => !item.isAbstract)
30 | .filter(item => {
31 | const implementsReferencedInterface =
32 | (item as ObjectTypeDefinition).interfaces &&
33 | (item as ObjectTypeDefinition).interfaces.some(i =>
34 | types.includes(i),
35 | );
36 | return types.includes(item.target) || implementsReferencedInterface;
37 | })
38 | .map(({ type }) => type);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/output-type.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { GraphQLOutputType } from 'graphql';
3 | import { BuildSchemaOptions, GqlTypeReference } from '../../interfaces';
4 | import { TypeOptions } from '../../interfaces/type-options.interface';
5 | import { CannotDetermineOutputTypeError } from '../errors/cannot-determine-output-type.error';
6 | import { TypeMapperSevice } from '../services/type-mapper.service';
7 | import { TypeDefinitionsStorage } from '../storages/type-definitions.storage';
8 |
9 | @Injectable()
10 | export class OutputTypeFactory {
11 | constructor(
12 | private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
13 | private readonly typeMapperService: TypeMapperSevice,
14 | ) {}
15 |
16 | public create(
17 | hostType: string,
18 | typeRef: GqlTypeReference,
19 | buildOptions: BuildSchemaOptions,
20 | typeOptions: TypeOptions = {},
21 | ): GraphQLOutputType {
22 | let gqlType:
23 | | GraphQLOutputType
24 | | undefined = this.typeMapperService.mapToScalarType(
25 | typeRef,
26 | buildOptions.scalarsMap,
27 | buildOptions.dateScalarMode,
28 | );
29 | if (!gqlType) {
30 | gqlType = this.typeDefinitionsStorage.getOutputTypeAndExtract(typeRef);
31 | if (!gqlType) {
32 | throw new CannotDetermineOutputTypeError(hostType);
33 | }
34 | }
35 | return this.typeMapperService.mapToGqlType(
36 | hostType,
37 | gqlType,
38 | typeOptions,
39 | false,
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/query-type.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { GraphQLObjectType } from 'graphql';
3 | import { BuildSchemaOptions } from '../../interfaces';
4 | import { TypeMetadataStorage } from '../storages/type-metadata.storage';
5 | import { RootTypeFactory } from './root-type.factory';
6 |
7 | @Injectable()
8 | export class QueryTypeFactory {
9 | constructor(private readonly rootTypeFactory: RootTypeFactory) {}
10 |
11 | public create(
12 | typeRefs: Function[],
13 | options: BuildSchemaOptions,
14 | ): GraphQLObjectType {
15 | const objectTypeName = 'Query';
16 | const queriesMetadata = TypeMetadataStorage.getQueriesMetadata();
17 |
18 | return this.rootTypeFactory.create(
19 | typeRefs,
20 | queriesMetadata,
21 | objectTypeName,
22 | options,
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/resolve-type.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { isString } from '@nestjs/common/utils/shared.utils';
3 | import { GraphQLTypeResolver } from 'graphql';
4 | import { ResolveTypeFn } from '../../interfaces';
5 | import { TypeDefinitionsStorage } from '../storages/type-definitions.storage';
6 |
7 | @Injectable()
8 | export class ResolveTypeFactory {
9 | constructor(
10 | private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
11 | ) {}
12 |
13 | public getResolveTypeFunction(
14 | resolveType: ResolveTypeFn,
15 | ): GraphQLTypeResolver {
16 | return async (...args) => {
17 | const resolvedType = await resolveType(...args);
18 | if (isString(resolvedType)) {
19 | return resolvedType;
20 | }
21 | const typeDef = this.typeDefinitionsStorage.getObjectTypeByTarget(
22 | resolvedType,
23 | );
24 | return typeDef?.type;
25 | };
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/root-type.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
3 | import { BuildSchemaOptions } from '../../interfaces';
4 | import { ResolverTypeMetadata } from '../metadata/resolver.metadata';
5 | import { OrphanedReferenceRegistry } from '../services/orphaned-reference.registry';
6 | import { ArgsFactory } from './args.factory';
7 | import { AstDefinitionNodeFactory } from './ast-definition-node.factory';
8 | import { OutputTypeFactory } from './output-type.factory';
9 |
10 | export type FieldsFactory = (
11 | handlers: ResolverTypeMetadata[],
12 | options: BuildSchemaOptions,
13 | ) => GraphQLFieldConfigMap;
14 |
15 | @Injectable()
16 | export class RootTypeFactory {
17 | constructor(
18 | private readonly outputTypeFactory: OutputTypeFactory,
19 | private readonly argsFactory: ArgsFactory,
20 | private readonly astDefinitionNodeFactory: AstDefinitionNodeFactory,
21 | private readonly orphanedReferenceRegistry: OrphanedReferenceRegistry,
22 | ) {}
23 |
24 | public create(
25 | typeRefs: Function[],
26 | resolversMetadata: ResolverTypeMetadata[],
27 | objectTypeName: 'Subscription' | 'Mutation' | 'Query',
28 | options: BuildSchemaOptions,
29 | fieldsFactory: FieldsFactory = (handlers) =>
30 | this.generateFields(handlers, options),
31 | ): GraphQLObjectType {
32 | const handlers = typeRefs
33 | ? resolversMetadata.filter((query) => typeRefs.includes(query.target))
34 | : resolversMetadata;
35 |
36 | if (handlers.length === 0) {
37 | return;
38 | }
39 | return new GraphQLObjectType({
40 | name: objectTypeName,
41 | fields: fieldsFactory(handlers, options),
42 | });
43 | }
44 |
45 | generateFields(
46 | handlers: ResolverTypeMetadata[],
47 | options: BuildSchemaOptions,
48 | ): GraphQLFieldConfigMap {
49 | const fieldConfigMap: GraphQLFieldConfigMap = {};
50 |
51 | handlers
52 | .filter(
53 | (handler) =>
54 | !(handler.classMetadata && handler.classMetadata.isAbstract),
55 | )
56 | .forEach((handler) => {
57 | this.orphanedReferenceRegistry.addToRegistryIfOrphaned(
58 | handler.typeFn(),
59 | );
60 |
61 | const type = this.outputTypeFactory.create(
62 | handler.methodName,
63 | handler.typeFn(),
64 | options,
65 | handler.returnTypeOptions,
66 | );
67 |
68 | const key = handler.schemaName;
69 | fieldConfigMap[key] = {
70 | type,
71 | args: this.argsFactory.create(handler.methodArgs, options),
72 | resolve: undefined,
73 | description: handler.description,
74 | deprecationReason: handler.deprecationReason,
75 | /**
76 | * AST node has to be manually created in order to define directives
77 | * (more on this topic here: https://github.com/graphql/graphql-js/issues/1343)
78 | */
79 | astNode: this.astDefinitionNodeFactory.createFieldNode(
80 | key,
81 | type,
82 | handler.directives,
83 | ),
84 | extensions: {
85 | complexity: handler.complexity,
86 | ...handler.extensions,
87 | },
88 | };
89 | });
90 | return fieldConfigMap;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/subscription-type.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { GraphQLObjectType } from 'graphql';
3 | import { BuildSchemaOptions } from '../../interfaces';
4 | import { TypeMetadataStorage } from '../storages/type-metadata.storage';
5 | import { RootTypeFactory } from './root-type.factory';
6 |
7 | @Injectable()
8 | export class SubscriptionTypeFactory {
9 | constructor(private readonly rootTypeFactory: RootTypeFactory) {}
10 |
11 | public create(
12 | typeRefs: Function[],
13 | options: BuildSchemaOptions,
14 | ): GraphQLObjectType {
15 | const objectTypeName = 'Subscription';
16 | const subscriptionsMetadata = TypeMetadataStorage.getSubscriptionsMetadata();
17 |
18 | return this.rootTypeFactory.create(
19 | typeRefs,
20 | subscriptionsMetadata,
21 | objectTypeName,
22 | options,
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/schema-builder/factories/union-definition.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Type } from '@nestjs/common';
2 | import { GraphQLUnionType } from 'graphql';
3 | import { ReturnTypeCannotBeResolvedError } from '../errors/return-type-cannot-be-resolved.error';
4 | import { UnionMetadata } from '../metadata';
5 | import { TypeDefinitionsStorage } from '../storages/type-definitions.storage';
6 | import { ResolveTypeFactory } from './resolve-type.factory';
7 |
8 | export interface UnionDefinition {
9 | id: symbol;
10 | type: GraphQLUnionType;
11 | }
12 |
13 | @Injectable()
14 | export class UnionDefinitionFactory {
15 | constructor(
16 | private readonly resolveTypeFactory: ResolveTypeFactory,
17 | private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
18 | ) {}
19 |
20 | public create(metadata: UnionMetadata): UnionDefinition {
21 | const getObjectType = (item: Type) =>
22 | this.typeDefinitionsStorage.getObjectTypeByTarget(item).type;
23 | const types = () => metadata.typesFn().map(item => getObjectType(item));
24 |
25 | return {
26 | id: metadata.id,
27 | type: new GraphQLUnionType({
28 | name: metadata.name,
29 | description: metadata.description,
30 | types,
31 | resolveType: this.createResolveTypeFn(metadata),
32 | }),
33 | };
34 | }
35 |
36 | private createResolveTypeFn(metadata: UnionMetadata) {
37 | return metadata.resolveType
38 | ? this.resolveTypeFactory.getResolveTypeFunction(metadata.resolveType)
39 | : (instance: any) => {
40 | const target = metadata
41 | .typesFn()
42 | .find(Type => instance instanceof Type);
43 |
44 | if (!target) {
45 | throw new ReturnTypeCannotBeResolvedError(metadata.name);
46 | }
47 | const objectDef = this.typeDefinitionsStorage.getObjectTypeByTarget(
48 | target,
49 | );
50 | return objectDef.type;
51 | };
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/schema-builder/graphql-schema.factory.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Logger, Type } from '@nestjs/common';
2 | import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils';
3 | import { getIntrospectionQuery, graphql, GraphQLSchema } from 'graphql';
4 | import { SCALAR_NAME_METADATA, SCALAR_TYPE_METADATA } from '../fgql.constants';
5 | import { BuildSchemaOptions, ScalarsTypeMap } from '../interfaces';
6 | import { createScalarType } from '../utils/scalar-types.utils';
7 | import { SchemaGenerationError } from './errors/schema-generation.error';
8 | import { MutationTypeFactory } from './factories/mutation-type.factory';
9 | import { OrphanedTypesFactory } from './factories/orphaned-types.factory';
10 | import { QueryTypeFactory } from './factories/query-type.factory';
11 | import { SubscriptionTypeFactory } from './factories/subscription-type.factory';
12 | import { LazyMetadataStorage } from './storages/lazy-metadata.storage';
13 | import { TypeMetadataStorage } from './storages/type-metadata.storage';
14 | import { TypeDefinitionsGenerator } from './type-definitions.generator';
15 |
16 | @Injectable()
17 | export class GraphQLSchemaFactory {
18 | private readonly logger = new Logger(GraphQLSchemaFactory.name);
19 |
20 | constructor(
21 | private readonly queryTypeFactory: QueryTypeFactory,
22 | private readonly mutationTypeFactory: MutationTypeFactory,
23 | private readonly subscriptionTypeFactory: SubscriptionTypeFactory,
24 | private readonly orphanedTypesFactory: OrphanedTypesFactory,
25 | private readonly typeDefinitionsGenerator: TypeDefinitionsGenerator,
26 | ) {}
27 |
28 | async create(resolvers: Function[]): Promise;
29 | async create(
30 | resolvers: Function[],
31 | scalarClasses: Function[],
32 | ): Promise;
33 | async create(
34 | resolvers: Function[],
35 | options: BuildSchemaOptions,
36 | ): Promise;
37 | async create(
38 | resolvers: Function[],
39 | scalarClasses: Function[],
40 | options: BuildSchemaOptions,
41 | ): Promise;
42 | async create(
43 | resolvers: Function[],
44 | scalarsOrOptions: Function[] | BuildSchemaOptions = [],
45 | options: BuildSchemaOptions = {},
46 | ): Promise {
47 | if (Array.isArray(scalarsOrOptions)) {
48 | this.assignScalarObjects(scalarsOrOptions, options);
49 | } else {
50 | options = scalarsOrOptions;
51 | }
52 |
53 | TypeMetadataStorage.clear();
54 | LazyMetadataStorage.load(resolvers);
55 | TypeMetadataStorage.compile(options.orphanedTypes);
56 |
57 | this.typeDefinitionsGenerator.generate(options);
58 |
59 | const schema = new GraphQLSchema({
60 | mutation: this.mutationTypeFactory.create(resolvers, options),
61 | query: this.queryTypeFactory.create(resolvers, options),
62 | subscription: this.subscriptionTypeFactory.create(resolvers, options),
63 | types: this.orphanedTypesFactory.create(options.orphanedTypes),
64 | directives: options.directives,
65 | });
66 |
67 | if (!options.skipCheck) {
68 | const introspectionQuery = getIntrospectionQuery();
69 | const { errors } = await graphql(schema, introspectionQuery);
70 | if (errors) {
71 | throw new SchemaGenerationError(errors);
72 | }
73 | }
74 |
75 | return schema;
76 | }
77 |
78 | private assignScalarObjects(
79 | scalars: Function[],
80 | options: BuildSchemaOptions,
81 | ) {
82 | if (isEmpty(scalars)) {
83 | return;
84 | }
85 | const scalarsMap = options.scalarsMap || [];
86 | scalars
87 | .filter((classRef) => classRef)
88 | .forEach((classRef) =>
89 | this.addScalarTypeByClassRef(classRef as Type, scalarsMap),
90 | );
91 |
92 | options.scalarsMap = scalarsMap;
93 | }
94 |
95 | private addScalarTypeByClassRef(
96 | classRef: Type,
97 | scalarsMap: ScalarsTypeMap[],
98 | ) {
99 | try {
100 | const scalarNameMetadata = Reflect.getMetadata(
101 | SCALAR_NAME_METADATA,
102 | classRef,
103 | );
104 | const scalarTypeMetadata = Reflect.getMetadata(
105 | SCALAR_TYPE_METADATA,
106 | classRef,
107 | );
108 | if (!scalarNameMetadata) {
109 | return;
110 | }
111 | const instance = new (classRef as Type)();
112 | const type =
113 | (isFunction(scalarTypeMetadata) && scalarTypeMetadata()) || classRef;
114 |
115 | scalarsMap.push({
116 | type,
117 | scalar: createScalarType(scalarNameMetadata, instance),
118 | });
119 | } catch {
120 | this.logger.error(
121 | `Cannot generate a GraphQLScalarType for "${classRef.name}" scalar. Make sure to put any initialization logic in the lifecycle hooks instead of a constructor.`,
122 | );
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/lib/schema-builder/helpers/file-system.helper.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import * as fs from 'fs';
3 | import { join, parse, resolve, sep } from 'path';
4 |
5 | @Injectable()
6 | export class FileSystemHelper {
7 | async writeFile(path: string, content: string) {
8 | try {
9 | await fs.promises.writeFile(path, content);
10 | } catch (err) {
11 | if (err.code !== 'ENOENT') {
12 | throw err;
13 | }
14 | await this.mkdirRecursive(path);
15 | await fs.promises.writeFile(path, content);
16 | }
17 | }
18 |
19 | async mkdirRecursive(path: string) {
20 | for (const dir of this.getDirs(path)) {
21 | try {
22 | await fs.promises.mkdir(dir);
23 | } catch (err) {
24 | if (err.code !== 'EEXIST') {
25 | throw err;
26 | }
27 | }
28 | }
29 | }
30 |
31 | getDirs(path: string): string[] {
32 | const parsedPath = parse(resolve(path));
33 | const chunks = parsedPath.dir.split(sep);
34 | if (parsedPath.root === '/') {
35 | chunks[0] = `/${chunks[0]}`;
36 | }
37 | const dirs = new Array();
38 | chunks.reduce((previous: string, next: string) => {
39 | const directory = join(previous, next);
40 | dirs.push(directory);
41 | return join(directory);
42 | });
43 | return dirs;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/schema-builder/helpers/get-default-value.helper.ts:
--------------------------------------------------------------------------------
1 | import { isUndefined } from '@nestjs/common/utils/shared.utils';
2 | import { TypeOptions } from '../../interfaces/type-options.interface';
3 | import { DefaultValuesConflictError } from '../errors/default-values-conflict.error';
4 |
5 | export function getDefaultValue(
6 | instance: object,
7 | options: TypeOptions,
8 | key: string,
9 | typeName: string,
10 | ): T | undefined {
11 | const initializerValue = instance[key];
12 | if (isUndefined(options.defaultValue)) {
13 | return initializerValue;
14 | }
15 | if (
16 | options.defaultValue !== initializerValue &&
17 | !isUndefined(initializerValue)
18 | ) {
19 | throw new DefaultValuesConflictError(
20 | typeName,
21 | key,
22 | options.defaultValue,
23 | initializerValue,
24 | );
25 | }
26 | return options.defaultValue;
27 | }
28 |
--------------------------------------------------------------------------------
/lib/schema-builder/index.ts:
--------------------------------------------------------------------------------
1 | export * from './storages';
2 | export * from './graphql-schema.factory';
3 | export * from './schema-builder.module';
4 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/class.metadata.ts:
--------------------------------------------------------------------------------
1 | import { DirectiveMetadata } from './directive.metadata';
2 | import { PropertyMetadata } from './property.metadata';
3 |
4 | export interface ClassMetadata {
5 | target: Function;
6 | name: string;
7 | description?: string;
8 | isAbstract?: boolean;
9 | directives?: DirectiveMetadata[];
10 | extensions?: Record;
11 | properties?: PropertyMetadata[];
12 | }
13 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/directive.metadata.ts:
--------------------------------------------------------------------------------
1 | export interface DirectiveMetadata {
2 | sdl: string;
3 | target: Function;
4 | }
5 |
6 | export type ClassDirectiveMetadata = DirectiveMetadata;
7 |
8 | export interface PropertyDirectiveMetadata extends DirectiveMetadata {
9 | fieldName: string;
10 | }
11 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/enum.metadata.ts:
--------------------------------------------------------------------------------
1 | export interface EnumMetadata {
2 | ref: object;
3 | name: string;
4 | description?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/extensions.metadata.ts:
--------------------------------------------------------------------------------
1 | export interface ExtensionsMetadata {
2 | target: Function;
3 | value: Record;
4 | }
5 |
6 | export type ClassExtensionsMetadata = ExtensionsMetadata;
7 |
8 | export interface PropertyExtensionsMetadata extends ExtensionsMetadata {
9 | fieldName: string;
10 | }
11 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/index.ts:
--------------------------------------------------------------------------------
1 | export * from './class.metadata';
2 | export * from './directive.metadata';
3 | export * from './enum.metadata';
4 | export * from './extensions.metadata';
5 | export * from './param.metadata';
6 | export * from './property.metadata';
7 | export * from './resolver.metadata';
8 | export * from './union.metadata';
9 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/interface.metadata.ts:
--------------------------------------------------------------------------------
1 | import { ResolveTypeFn } from '../../interfaces';
2 | import { ClassMetadata } from './class.metadata';
3 |
4 | export interface InterfaceMetadata extends ClassMetadata {
5 | resolveType?: ResolveTypeFn;
6 | }
7 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/object-type.metadata.ts:
--------------------------------------------------------------------------------
1 | import { ClassMetadata } from './class.metadata';
2 |
3 | export interface ObjectTypeMetadata extends ClassMetadata {
4 | interfaces?: Function[];
5 | }
6 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/param.metadata.ts:
--------------------------------------------------------------------------------
1 | import { GqlTypeReference } from '../../interfaces';
2 | import { TypeOptions } from '../../interfaces/type-options.interface';
3 |
4 | export interface BaseArgMetadata {
5 | target: Function;
6 | methodName: string;
7 | typeFn: (type?: any) => GqlTypeReference;
8 | options: TypeOptions;
9 | index: number;
10 | }
11 |
12 | export interface ArgParamMetadata extends BaseArgMetadata {
13 | kind: 'arg';
14 | name: string;
15 | description?: string;
16 | }
17 |
18 | export interface ArgsParamMetadata extends BaseArgMetadata {
19 | kind: 'args';
20 | }
21 |
22 | export type MethodArgsMetadata = ArgParamMetadata | ArgsParamMetadata;
23 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/property.metadata.ts:
--------------------------------------------------------------------------------
1 | import { Complexity, GqlTypeReference } from '../../interfaces';
2 | import { TypeOptions } from '../../interfaces/type-options.interface';
3 | import { DirectiveMetadata } from './directive.metadata';
4 | import { MethodArgsMetadata } from './param.metadata';
5 |
6 | export interface PropertyMetadata {
7 | schemaName: string;
8 | name: string;
9 | typeFn: () => GqlTypeReference;
10 | target: Function;
11 | options: TypeOptions;
12 | description?: string;
13 | deprecationReason?: string;
14 | methodArgs?: MethodArgsMetadata[];
15 | directives?: DirectiveMetadata[];
16 | extensions?: Record;
17 | complexity?: Complexity;
18 | }
19 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/resolver.metadata.ts:
--------------------------------------------------------------------------------
1 | import { Type } from '@nestjs/common';
2 | import { Complexity, GqlTypeReference } from '../../interfaces';
3 | import { TypeOptions } from '../../interfaces/type-options.interface';
4 | import { DirectiveMetadata } from './directive.metadata';
5 | import { MethodArgsMetadata } from './param.metadata';
6 |
7 | export interface ResolverClassMetadata {
8 | target: Function;
9 | typeFn: (of?: void) => Type;
10 | isAbstract?: boolean;
11 | parent?: ResolverClassMetadata;
12 | }
13 |
14 | export interface BaseResolverMetadata {
15 | target: Function;
16 | methodName: string;
17 | schemaName: string;
18 | description?: string;
19 | deprecationReason?: string;
20 | methodArgs?: MethodArgsMetadata[];
21 | classMetadata?: ResolverClassMetadata;
22 | directives?: DirectiveMetadata[];
23 | extensions?: Record;
24 | complexity?: Complexity;
25 | }
26 |
27 | export interface ResolverTypeMetadata extends BaseResolverMetadata {
28 | typeFn: (type?: void) => GqlTypeReference;
29 | returnTypeOptions: TypeOptions;
30 | }
31 |
32 | export interface FieldResolverMetadata extends BaseResolverMetadata {
33 | kind: 'internal' | 'external';
34 | typeOptions?: TypeOptions;
35 | typeFn?: (type?: void) => GqlTypeReference;
36 | objectTypeFn?: (of?: void) => Type;
37 | }
38 |
--------------------------------------------------------------------------------
/lib/schema-builder/metadata/union.metadata.ts:
--------------------------------------------------------------------------------
1 | import { Type } from '@nestjs/common';
2 | import { ResolveTypeFn } from '../../interfaces';
3 |
4 | export interface UnionMetadata[] = Type[]> {
5 | name: string;
6 | typesFn: () => T;
7 | id?: symbol;
8 | description?: string;
9 | resolveType?: ResolveTypeFn;
10 | }
11 |
--------------------------------------------------------------------------------
/lib/schema-builder/schema-builder.module.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The API surface of this module has been heavily inspired by the "type-graphql" library (https://github.com/MichalLytek/type-graphql), originally designed & released by Michal Lytek.
3 | * In the v6 major release of NestJS, we introduced the code-first approach as a compatibility layer between this package and the `@nestjs/graphql` module.
4 | * Eventually, our team decided to reimplement all the features from scratch due to a lack of flexibility.
5 | * To avoid numerous breaking changes, the public API is backward-compatible and may resemble "type-graphql".
6 | */
7 |
8 | import { Module } from '@nestjs/common';
9 | import { schemaBuilderFactories } from './factories/factories';
10 | import { GraphQLSchemaFactory } from './graphql-schema.factory';
11 | import { FileSystemHelper } from './helpers/file-system.helper';
12 | import { OrphanedReferenceRegistry } from './services/orphaned-reference.registry';
13 | import { TypeFieldsAccessor } from './services/type-fields.accessor';
14 | import { TypeMapperSevice } from './services/type-mapper.service';
15 | import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
16 | import { TypeDefinitionsGenerator } from './type-definitions.generator';
17 |
18 | @Module({
19 | providers: [
20 | ...schemaBuilderFactories,
21 | GraphQLSchemaFactory,
22 | TypeDefinitionsGenerator,
23 | FileSystemHelper,
24 | TypeDefinitionsStorage,
25 | TypeMapperSevice,
26 | TypeFieldsAccessor,
27 | OrphanedReferenceRegistry,
28 | ],
29 | exports: [GraphQLSchemaFactory, FileSystemHelper],
30 | })
31 | export class GraphQLSchemaBuilderModule {}
32 |
--------------------------------------------------------------------------------
/lib/schema-builder/services/orphaned-reference.registry.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { isFunction } from '@nestjs/common/utils/shared.utils';
3 | import { GqlTypeReference } from '../../interfaces';
4 |
5 | const BANNED_TYPES: Function[] = [String, Date, Number, Boolean];
6 |
7 | @Injectable()
8 | export class OrphanedReferenceRegistry {
9 | private readonly registry = new Set();
10 |
11 | addToRegistryIfOrphaned(typeRef: GqlTypeReference) {
12 | if (!isFunction(typeRef)) {
13 | return;
14 | }
15 | if (BANNED_TYPES.includes(typeRef as Function)) {
16 | return;
17 | }
18 | this.registry.add(typeRef as Function);
19 | }
20 |
21 | getAll(): Function[] {
22 | return [...this.registry.values()];
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/schema-builder/services/type-fields.accessor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import {
3 | GraphQLFieldConfigArgumentMap,
4 | GraphQLFieldConfigMap,
5 | GraphQLInputFieldConfigMap,
6 | GraphQLInputObjectType,
7 | GraphQLInterfaceType,
8 | GraphQLObjectType,
9 | } from 'graphql';
10 | import { omit } from 'lodash';
11 |
12 | @Injectable()
13 | export class TypeFieldsAccessor {
14 | extractFromInputType(
15 | gqlType: GraphQLInputObjectType,
16 | ): GraphQLInputFieldConfigMap {
17 | const fieldsMap = gqlType.getFields();
18 | const fieldsConfig: GraphQLInputFieldConfigMap = {};
19 |
20 | for (const key in fieldsMap) {
21 | const targetField = fieldsMap[key];
22 | fieldsConfig[key] = {
23 | type: targetField.type,
24 | description: targetField.description,
25 | defaultValue: targetField.defaultValue,
26 | astNode: targetField.astNode,
27 | extensions: targetField.extensions,
28 | };
29 | }
30 | return fieldsConfig;
31 | }
32 |
33 | extractFromInterfaceOrObjectType(
34 | type: GraphQLInterfaceType | GraphQLObjectType,
35 | ): GraphQLFieldConfigMap {
36 | const fieldsMap = type.getFields();
37 | const fieldsConfig: GraphQLFieldConfigMap = {};
38 |
39 | for (const key in fieldsMap) {
40 | const targetField = fieldsMap[key];
41 | const args: GraphQLFieldConfigArgumentMap = {};
42 | targetField.args.forEach((item) => {
43 | args[item.name] = omit(item, 'name');
44 | });
45 |
46 | fieldsConfig[key] = {
47 | type: targetField.type,
48 | description: targetField.description,
49 | deprecationReason: targetField.deprecationReason,
50 | extensions: targetField.extensions,
51 | astNode: targetField.astNode,
52 | resolve: targetField.resolve,
53 | args,
54 | };
55 | }
56 |
57 | return fieldsConfig;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/lib/schema-builder/services/type-mapper.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Type } from '@nestjs/common';
2 | import { isUndefined } from '@nestjs/common/utils/shared.utils';
3 | import {
4 | GraphQLBoolean,
5 | GraphQLFloat,
6 | GraphQLList,
7 | GraphQLNonNull,
8 | GraphQLScalarType,
9 | GraphQLString,
10 | GraphQLType,
11 | } from 'graphql';
12 | import {
13 | DateScalarMode,
14 | GqlTypeReference,
15 | ScalarsTypeMap,
16 | } from '../../interfaces';
17 | import { TypeOptions } from '../../interfaces/type-options.interface';
18 | import { GraphQLISODateTime, GraphQLTimestamp } from '../../scalars';
19 | import { DefaultNullableConflictError } from '../errors/default-nullable-conflict.error';
20 | import { InvalidNullableOptionError } from '../errors/invalid-nullable-option.error';
21 |
22 | @Injectable()
23 | export class TypeMapperSevice {
24 | mapToScalarType>(
25 | typeRef: T,
26 | scalarsMap: ScalarsTypeMap[] = [],
27 | dateScalarMode: DateScalarMode = 'isoDate',
28 | ): GraphQLScalarType | undefined {
29 | if (typeRef instanceof GraphQLScalarType) {
30 | return typeRef;
31 | }
32 | const scalarHost = scalarsMap.find((item) => item.type === typeRef);
33 | if (scalarHost) {
34 | return scalarHost.scalar;
35 | }
36 | const dateScalar =
37 | dateScalarMode === 'timestamp' ? GraphQLTimestamp : GraphQLISODateTime;
38 | const typeScalarMapping = new Map([
39 | [String, GraphQLString],
40 | [Number, GraphQLFloat],
41 | [Boolean, GraphQLBoolean],
42 | [Date, dateScalar],
43 | ]);
44 | return typeScalarMapping.get(typeRef as Function);
45 | }
46 |
47 | mapToGqlType(
48 | hostType: string,
49 | typeRef: T,
50 | options: TypeOptions,
51 | isInputTypeCtx: boolean,
52 | ): T {
53 | this.validateTypeOptions(hostType, options);
54 | let graphqlType: T | GraphQLList | GraphQLNonNull = typeRef;
55 |
56 | if (options.isArray) {
57 | graphqlType = this.mapToGqlList(
58 | graphqlType,
59 | options.arrayDepth!,
60 | this.hasArrayOptions(options),
61 | );
62 | }
63 |
64 | let isNotNullable: boolean;
65 | if (isInputTypeCtx) {
66 | /**
67 | * The input values (e.g., args) remain "nullable"
68 | * even if the "defaultValue" is specified.
69 | */
70 | isNotNullable =
71 | isUndefined(options.defaultValue) &&
72 | (!options.nullable || options.nullable === 'items');
73 | } else {
74 | isNotNullable = !options.nullable || options.nullable === 'items';
75 | }
76 | return isNotNullable
77 | ? (new GraphQLNonNull(graphqlType) as T)
78 | : (graphqlType as T);
79 | }
80 |
81 | private validateTypeOptions(hostType: string, options: TypeOptions) {
82 | if (!options.isArray && this.hasArrayOptions(options)) {
83 | throw new InvalidNullableOptionError(hostType, options.nullable);
84 | }
85 |
86 | const isNotNullable = options.nullable === 'items';
87 | if (!isUndefined(options.defaultValue) && isNotNullable) {
88 | throw new DefaultNullableConflictError(
89 | hostType,
90 | options.defaultValue,
91 | options.nullable,
92 | );
93 | }
94 | return true;
95 | }
96 |
97 | private mapToGqlList(
98 | targetType: T,
99 | depth: number,
100 | nullable: boolean,
101 | ): GraphQLList {
102 | const targetTypeNonNull = nullable
103 | ? targetType
104 | : new GraphQLNonNull(targetType);
105 |
106 | if (depth === 0) {
107 | return targetType as GraphQLList;
108 | }
109 | return this.mapToGqlList(
110 | new GraphQLList(targetTypeNonNull) as T,
111 | depth - 1,
112 | nullable,
113 | );
114 | }
115 |
116 | private hasArrayOptions(options: TypeOptions) {
117 | return options.nullable === 'items' || options.nullable === 'itemsAndList';
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/lib/schema-builder/storages/index.ts:
--------------------------------------------------------------------------------
1 | export { TypeMetadataStorage } from './type-metadata.storage';
2 |
--------------------------------------------------------------------------------
/lib/schema-builder/storages/lazy-metadata.storage.ts:
--------------------------------------------------------------------------------
1 | import { flatten, Type } from '@nestjs/common';
2 | import { isUndefined } from '@nestjs/common/utils/shared.utils';
3 |
4 | interface LazyMetadataHost {
5 | func: Function;
6 | target?: Type;
7 | }
8 |
9 | export class LazyMetadataStorageHost {
10 | private readonly storage = new Array();
11 |
12 | store(func: Function): void;
13 | store(target: Type, func: Function): void;
14 | store(targetOrFn: Type | Function, func?: Function) {
15 | if (func) {
16 | this.storage.push({ target: targetOrFn as Type, func });
17 | } else {
18 | this.storage.push({ func: targetOrFn });
19 | }
20 | }
21 |
22 | load(types: Function[] = []) {
23 | types = this.concatPrototypes(types);
24 | this.storage.forEach(({ func, target }) => {
25 | if (target && types.includes(target)) {
26 | func();
27 | } else if (!target) {
28 | func();
29 | }
30 | });
31 | }
32 |
33 | private concatPrototypes(types: Function[]): Function[] {
34 | const typesWithPrototypes = types
35 | .filter((type) => type && type.prototype)
36 | .map((type) => {
37 | const parentTypes = [];
38 |
39 | let parent: Function = type;
40 | while (!isUndefined(parent.prototype)) {
41 | parent = Object.getPrototypeOf(parent);
42 | if (parent === Function.prototype) {
43 | break;
44 | }
45 | parentTypes.push(parent);
46 | }
47 | parentTypes.push(type);
48 | return parentTypes;
49 | });
50 |
51 | return flatten(typesWithPrototypes);
52 | }
53 | }
54 |
55 | const globalRef = global as any;
56 | export const LazyMetadataStorage: LazyMetadataStorageHost =
57 | globalRef.GqlLazyMetadataStorageHost ||
58 | (globalRef.GqlLazyMetadataStorageHost = new LazyMetadataStorageHost());
59 |
--------------------------------------------------------------------------------
/lib/schema-builder/storages/type-definitions.storage.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import {
3 | GraphQLEnumType,
4 | GraphQLInputObjectType,
5 | GraphQLInterfaceType,
6 | GraphQLObjectType,
7 | GraphQLUnionType,
8 | } from 'graphql';
9 | import { EnumDefinition } from '../factories/enum-definition.factory';
10 | import { InputTypeDefinition } from '../factories/input-type-definition.factory';
11 | import { InterfaceTypeDefinition } from '../factories/interface-definition.factory';
12 | import { ObjectTypeDefinition } from '../factories/object-type-definition.factory';
13 | import { UnionDefinition } from '../factories/union-definition.factory';
14 |
15 | export type GqlInputTypeKey = Function | object;
16 | export type GqlInputType = InputTypeDefinition | EnumDefinition;
17 |
18 | export type GqlOutputTypeKey = Function | object | symbol;
19 | export type GqlOutputType =
20 | | InterfaceTypeDefinition
21 | | ObjectTypeDefinition
22 | | EnumDefinition
23 | | UnionDefinition;
24 |
25 | @Injectable()
26 | export class TypeDefinitionsStorage {
27 | private readonly interfaceTypeDefinitions = new Map<
28 | Function,
29 | InterfaceTypeDefinition
30 | >();
31 | private readonly enumTypeDefinitions = new Map