Skip to content

Commit cda1ba3

Browse files
authored
Add new plugin for NestJS (#548)
* Add new plugin for NestJS * Add changeset
1 parent fa53f8f commit cda1ba3

12 files changed

Lines changed: 1525 additions & 1 deletion

File tree

.changeset/green-rockets-wash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@graphql-codegen/typescript-nest': major
3+
---
4+
5+
Add GraphQL Code Generator plugin for generating NestJS compatible types

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ plugins:
6161
`@graphql-codegen/java-resolvers`
6262
- [![npm version](https://badge.fury.io/js/%40graphql-codegen%2Fjava-apollo-android.svg)](https://badge.fury.io/js/%40graphql-codegen%2Fjava-apollo-android) -
6363
`@graphql-codegen/java-apollo-android`
64+
- [![npm version](https://badge.fury.io/js/%40graphql-codegen%2Ftypescript-nest.svg)](https://badge.fury.io/js/%40graphql-codegen%2Ftypescript-nest) -
65+
`@graphql-codegen/typescript-nest`
6466

6567
### Getting started with codegen
6668

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../../../../jest.project')({ dirname: __dirname });
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "@graphql-codegen/typescript-nest",
3+
"version": "0.0.0",
4+
"description": "GraphQL Code Generator plugin for generating NestJS compatible types",
5+
"repository": {
6+
"type": "git",
7+
"url": "https://github.com/dotansimha/graphql-code-generator-community.git",
8+
"directory": "packages/plugins/typescript/nest"
9+
},
10+
"license": "MIT",
11+
"engines": {
12+
"node": ">= 16.0.0"
13+
},
14+
"main": "dist/cjs/index.js",
15+
"module": "dist/esm/index.js",
16+
"exports": {
17+
".": {
18+
"require": {
19+
"types": "./dist/typings/index.d.cts",
20+
"default": "./dist/cjs/index.js"
21+
},
22+
"import": {
23+
"types": "./dist/typings/index.d.ts",
24+
"default": "./dist/esm/index.js"
25+
},
26+
"default": {
27+
"types": "./dist/typings/index.d.ts",
28+
"default": "./dist/esm/index.js"
29+
}
30+
},
31+
"./package.json": "./package.json"
32+
},
33+
"typings": "dist/typings/index.d.ts",
34+
"scripts": {
35+
"lint": "eslint **/*.ts",
36+
"test": "jest --no-watchman --config ../../../../jest.config.js"
37+
},
38+
"peerDependencies": {
39+
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
40+
},
41+
"dependencies": {
42+
"@graphql-codegen/plugin-helpers": "^3.0.0",
43+
"@graphql-codegen/typescript": "^2.8.1",
44+
"@graphql-codegen/visitor-plugin-common": "2.13.1",
45+
"auto-bind": "~4.0.0",
46+
"tslib": "~2.6.0"
47+
},
48+
"devDependencies": {
49+
"@graphql-codegen/testing": "1.18.0"
50+
},
51+
"publishConfig": {
52+
"directory": "dist",
53+
"access": "public"
54+
},
55+
"typescript": {
56+
"definition": "dist/typings/index.d.ts"
57+
}
58+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { TypeScriptPluginConfig } from '@graphql-codegen/typescript';
2+
import { DecoratorConfig } from './types';
3+
4+
export interface NestPluginConfig extends TypeScriptPluginConfig {
5+
/**
6+
* @name decoratorName
7+
* @description allow overriding of Nest decorator types
8+
* @default { type: 'ObjectType', interface: 'InterfaceType', arguments: 'ArgsType', field: 'Field', input: 'InputType' }
9+
*/
10+
decoratorName?: Partial<DecoratorConfig>;
11+
12+
/**
13+
* @name decorateTypes
14+
* @description Specifies the objects that will have Nest decorators prepended to them, by name. Non-matching types will still be output, but without decorators. If not set, all types will be decorated.
15+
* @exampleMarkdown Decorate only type User
16+
* ```ts filename="codegen.ts"
17+
* import type { CodegenConfig } from '@graphql-codegen/cli';
18+
*
19+
* const config: CodegenConfig = {
20+
* // ...
21+
* generates: {
22+
* 'path/to/file.ts': {
23+
* plugins: ['typescript-nest'],
24+
* config: {
25+
* decorateTypes: ['User']
26+
* },
27+
* },
28+
* },
29+
* };
30+
* export default config;
31+
* ```
32+
*/
33+
decorateTypes?: string[];
34+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const MAYBE_REGEX = /^Maybe<(.*?)>$/;
2+
export const ARRAY_REGEX = /^Array<(.*?)>$/;
3+
export const SCALAR_REGEX = /^Scalars\['(.*?)'\]$/;
4+
export const GRAPHQL_TYPES = ['Query', 'Mutation', 'Subscription'];
5+
export const NEST_SCALARS = ['ID', 'Int', 'Float', 'GraphQLISODateTime', 'GraphQLTimestamp'];
6+
export const FIX_DECORATOR_SIGNATURE = `type FixDecorator<T> = T;`;
7+
export const SCALARS = ['ID', 'String', 'Boolean', 'Int', 'Float'];
8+
export const NEST_PREFIX = 'Nest';
9+
export const NEST_IMPORT = `import * as ${NEST_PREFIX} from '@nestjs/graphql';\nexport { ${NEST_PREFIX} };`;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { GraphQLSchema } from 'graphql';
2+
import {
3+
getCachedDocumentNodeFromSchema,
4+
oldVisit,
5+
PluginFunction,
6+
Types,
7+
} from '@graphql-codegen/plugin-helpers';
8+
import {
9+
includeIntrospectionTypesDefinitions,
10+
TsIntrospectionVisitor,
11+
} from '@graphql-codegen/typescript';
12+
import { NestPluginConfig } from './config';
13+
import { NEST_IMPORT } from './constants';
14+
import { isDefinitionInterface } from './utils';
15+
import { NestVisitor } from './visitor';
16+
17+
export const plugin: PluginFunction<Partial<NestPluginConfig>, Types.ComplexPluginOutput> = (
18+
schema: GraphQLSchema,
19+
documents: Types.DocumentFile[],
20+
config: NestPluginConfig,
21+
) => {
22+
const visitor = new NestVisitor(schema, config);
23+
const astNode = getCachedDocumentNodeFromSchema(schema);
24+
const visitorResult = oldVisit(astNode, { leave: visitor });
25+
const introspectionDefinitions = includeIntrospectionTypesDefinitions(schema, documents, config);
26+
const scalars = visitor.scalarsDefinition;
27+
28+
const { definitions } = visitorResult;
29+
// Sort output by interfaces first, classes last to prevent TypeScript errors
30+
definitions.sort(
31+
(definition1, definition2) =>
32+
+isDefinitionInterface(definition2) - +isDefinitionInterface(definition1),
33+
);
34+
35+
return {
36+
prepend: [...visitor.getEnumsImports(), ...visitor.getWrapperDefinitions(), NEST_IMPORT],
37+
content: [scalars, ...definitions, ...introspectionDefinitions].join('\n'),
38+
};
39+
};
40+
41+
export { TsIntrospectionVisitor };
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export type DecoratorConfig = {
2+
type: string;
3+
interface: string;
4+
field: string;
5+
input: string;
6+
arguments: string;
7+
};
8+
9+
export type DecoratorOptions = {
10+
nullable?: string;
11+
description?: string;
12+
implements?: string;
13+
};
14+
15+
export interface Type {
16+
type: string;
17+
isNullable: boolean;
18+
isArray: boolean;
19+
isScalar: boolean;
20+
isItemsNullable: boolean;
21+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { DecoratorOptions, Type } from './types';
2+
3+
export const isDefinitionInterface = definition => definition.includes('@Nest.InterfaceType()');
4+
5+
export const escapeString = (str: string) =>
6+
"'" +
7+
String(str || '')
8+
.replace(/\\/g, '\\\\')
9+
.replace(/'/g, "\\'") +
10+
"'";
11+
12+
export const formatDecoratorOptions = (options: DecoratorOptions, isFirstArgument = true) => {
13+
if (!Object.keys(options).length) {
14+
return '';
15+
}
16+
17+
return (
18+
(isFirstArgument ? '' : ', ') +
19+
('{ ' +
20+
Object.entries(options)
21+
.map(([key, value]) => `${key}: ${JSON.stringify(value).replace(/"/g, '')}`)
22+
.join(', ') +
23+
' }')
24+
);
25+
};
26+
27+
export const buildTypeString = (type: Type): string => {
28+
if (!type.isArray && !type.isScalar && !type.isNullable) {
29+
type.type = `FixDecorator<${type.type}>`;
30+
}
31+
if (type.isScalar) {
32+
type.type = `Scalars['${type.type}']`;
33+
}
34+
if (type.isArray) {
35+
type.type = `Array<${type.type}>`;
36+
}
37+
if (type.isNullable) {
38+
type.type = `Maybe<${type.type}>`;
39+
}
40+
41+
return type.type;
42+
};
43+
44+
export const fixDecorator = (type: Type, typeString: string): string => {
45+
return type.isArray || type.isNullable || type.isScalar
46+
? typeString
47+
: `FixDecorator<${typeString}>`;
48+
};
49+
50+
export const getNestNullableValue = (type: Type): string => {
51+
if (type.isNullable) {
52+
if (type.isItemsNullable) {
53+
return "'itemsAndList'";
54+
}
55+
return 'true';
56+
}
57+
if (type.isItemsNullable) {
58+
return "'items'";
59+
}
60+
61+
return undefined;
62+
};

0 commit comments

Comments
 (0)