Skip to content

Commit

Permalink
Add new plugin for NestJS (#548)
Browse files Browse the repository at this point in the history
* Add new plugin for NestJS

* Add changeset
  • Loading branch information
bridges-wood authored Feb 2, 2024
1 parent fa53f8f commit cda1ba3
Show file tree
Hide file tree
Showing 12 changed files with 1,525 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/green-rockets-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-codegen/typescript-nest': major
---

Add GraphQL Code Generator plugin for generating NestJS compatible types
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ plugins:
`@graphql-codegen/java-resolvers`
- [![npm version](https://badge.fury.io/js/%40graphql-codegen%2Fjava-apollo-android.svg)](https://badge.fury.io/js/%40graphql-codegen%2Fjava-apollo-android) -
`@graphql-codegen/java-apollo-android`
- [![npm version](https://badge.fury.io/js/%40graphql-codegen%2Ftypescript-nest.svg)](https://badge.fury.io/js/%40graphql-codegen%2Ftypescript-nest) -
`@graphql-codegen/typescript-nest`

### Getting started with codegen

Expand Down
1 change: 1 addition & 0 deletions packages/plugins/typescript/nest/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('../../../../jest.project')({ dirname: __dirname });
58 changes: 58 additions & 0 deletions packages/plugins/typescript/nest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "@graphql-codegen/typescript-nest",
"version": "0.0.0",
"description": "GraphQL Code Generator plugin for generating NestJS compatible types",
"repository": {
"type": "git",
"url": "https://github.com/dotansimha/graphql-code-generator-community.git",
"directory": "packages/plugins/typescript/nest"
},
"license": "MIT",
"engines": {
"node": ">= 16.0.0"
},
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"exports": {
".": {
"require": {
"types": "./dist/typings/index.d.cts",
"default": "./dist/cjs/index.js"
},
"import": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
},
"default": {
"types": "./dist/typings/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"./package.json": "./package.json"
},
"typings": "dist/typings/index.d.ts",
"scripts": {
"lint": "eslint **/*.ts",
"test": "jest --no-watchman --config ../../../../jest.config.js"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
},
"dependencies": {
"@graphql-codegen/plugin-helpers": "^3.0.0",
"@graphql-codegen/typescript": "^2.8.1",
"@graphql-codegen/visitor-plugin-common": "2.13.1",
"auto-bind": "~4.0.0",
"tslib": "~2.6.0"
},
"devDependencies": {
"@graphql-codegen/testing": "1.18.0"
},
"publishConfig": {
"directory": "dist",
"access": "public"
},
"typescript": {
"definition": "dist/typings/index.d.ts"
}
}
34 changes: 34 additions & 0 deletions packages/plugins/typescript/nest/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { TypeScriptPluginConfig } from '@graphql-codegen/typescript';
import { DecoratorConfig } from './types';

export interface NestPluginConfig extends TypeScriptPluginConfig {
/**
* @name decoratorName
* @description allow overriding of Nest decorator types
* @default { type: 'ObjectType', interface: 'InterfaceType', arguments: 'ArgsType', field: 'Field', input: 'InputType' }
*/
decoratorName?: Partial<DecoratorConfig>;

/**
* @name decorateTypes
* @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.
* @exampleMarkdown Decorate only type User
* ```ts filename="codegen.ts"
* import type { CodegenConfig } from '@graphql-codegen/cli';
*
* const config: CodegenConfig = {
* // ...
* generates: {
* 'path/to/file.ts': {
* plugins: ['typescript-nest'],
* config: {
* decorateTypes: ['User']
* },
* },
* },
* };
* export default config;
* ```
*/
decorateTypes?: string[];
}
9 changes: 9 additions & 0 deletions packages/plugins/typescript/nest/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const MAYBE_REGEX = /^Maybe<(.*?)>$/;
export const ARRAY_REGEX = /^Array<(.*?)>$/;
export const SCALAR_REGEX = /^Scalars\['(.*?)'\]$/;
export const GRAPHQL_TYPES = ['Query', 'Mutation', 'Subscription'];
export const NEST_SCALARS = ['ID', 'Int', 'Float', 'GraphQLISODateTime', 'GraphQLTimestamp'];
export const FIX_DECORATOR_SIGNATURE = `type FixDecorator<T> = T;`;
export const SCALARS = ['ID', 'String', 'Boolean', 'Int', 'Float'];
export const NEST_PREFIX = 'Nest';
export const NEST_IMPORT = `import * as ${NEST_PREFIX} from '@nestjs/graphql';\nexport { ${NEST_PREFIX} };`;
41 changes: 41 additions & 0 deletions packages/plugins/typescript/nest/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { GraphQLSchema } from 'graphql';
import {
getCachedDocumentNodeFromSchema,
oldVisit,
PluginFunction,
Types,
} from '@graphql-codegen/plugin-helpers';
import {
includeIntrospectionTypesDefinitions,
TsIntrospectionVisitor,
} from '@graphql-codegen/typescript';
import { NestPluginConfig } from './config';
import { NEST_IMPORT } from './constants';
import { isDefinitionInterface } from './utils';
import { NestVisitor } from './visitor';

export const plugin: PluginFunction<Partial<NestPluginConfig>, Types.ComplexPluginOutput> = (
schema: GraphQLSchema,
documents: Types.DocumentFile[],
config: NestPluginConfig,
) => {
const visitor = new NestVisitor(schema, config);
const astNode = getCachedDocumentNodeFromSchema(schema);
const visitorResult = oldVisit(astNode, { leave: visitor });
const introspectionDefinitions = includeIntrospectionTypesDefinitions(schema, documents, config);
const scalars = visitor.scalarsDefinition;

const { definitions } = visitorResult;
// Sort output by interfaces first, classes last to prevent TypeScript errors
definitions.sort(
(definition1, definition2) =>
+isDefinitionInterface(definition2) - +isDefinitionInterface(definition1),
);

return {
prepend: [...visitor.getEnumsImports(), ...visitor.getWrapperDefinitions(), NEST_IMPORT],
content: [scalars, ...definitions, ...introspectionDefinitions].join('\n'),
};
};

export { TsIntrospectionVisitor };
21 changes: 21 additions & 0 deletions packages/plugins/typescript/nest/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export type DecoratorConfig = {
type: string;
interface: string;
field: string;
input: string;
arguments: string;
};

export type DecoratorOptions = {
nullable?: string;
description?: string;
implements?: string;
};

export interface Type {
type: string;
isNullable: boolean;
isArray: boolean;
isScalar: boolean;
isItemsNullable: boolean;
}
62 changes: 62 additions & 0 deletions packages/plugins/typescript/nest/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { DecoratorOptions, Type } from './types';

export const isDefinitionInterface = definition => definition.includes('@Nest.InterfaceType()');

export const escapeString = (str: string) =>
"'" +
String(str || '')
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'") +
"'";

export const formatDecoratorOptions = (options: DecoratorOptions, isFirstArgument = true) => {
if (!Object.keys(options).length) {
return '';
}

return (
(isFirstArgument ? '' : ', ') +
('{ ' +
Object.entries(options)
.map(([key, value]) => `${key}: ${JSON.stringify(value).replace(/"/g, '')}`)
.join(', ') +
' }')
);
};

export const buildTypeString = (type: Type): string => {
if (!type.isArray && !type.isScalar && !type.isNullable) {
type.type = `FixDecorator<${type.type}>`;
}
if (type.isScalar) {
type.type = `Scalars['${type.type}']`;
}
if (type.isArray) {
type.type = `Array<${type.type}>`;
}
if (type.isNullable) {
type.type = `Maybe<${type.type}>`;
}

return type.type;
};

export const fixDecorator = (type: Type, typeString: string): string => {
return type.isArray || type.isNullable || type.isScalar
? typeString
: `FixDecorator<${typeString}>`;
};

export const getNestNullableValue = (type: Type): string => {
if (type.isNullable) {
if (type.isItemsNullable) {
return "'itemsAndList'";
}
return 'true';
}
if (type.isItemsNullable) {
return "'items'";
}

return undefined;
};
Loading

0 comments on commit cda1ba3

Please sign in to comment.