Skip to content

Commit

Permalink
feat: add support for input types
Browse files Browse the repository at this point in the history
  • Loading branch information
rfermann committed Apr 17, 2021
1 parent e365523 commit 050472d
Show file tree
Hide file tree
Showing 12 changed files with 3,613 additions and 64 deletions.
13 changes: 4 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,13 @@ generator nestjs {

## Currently supported objects

- [Enums](#Enums)
- [Models](#Models)
- Enums
- Models
- Input Types

## Usage

### Enums

Enums can be used like regular [NestJS GraphQL Enums](https://docs.nestjs.com/graphql/unions-and-enums#enums)

### Models

Models are generated as usual NestJS GraphQL Object Types and can be used as any other Object Types.
All supported objects can be used as the regular NestJS GraphQL object equivalents.

## Contributing to NestJS-Prisma-GraphQL-Generator

Expand Down
11 changes: 9 additions & 2 deletions src/Generator/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ describe("Generator", () => {
return 0;
});

expect([...folderList]).toStrictEqual(["enums", "Session", "User"]);
expect([...folderList]).toStrictEqual(["enums", "Session", "shared", "User"]);
});
it("should log the correct actions", async () => {
expect.assertions(17);
expect.assertions(23);

const generator = new Generator({
datamodel: "",
Expand Down Expand Up @@ -130,5 +130,12 @@ describe("Generator", () => {
expect(findCompletedMessage(values, Generator.messages.models.parse)).toHaveLength(1);
expect(findStartedMessage(values, Generator.messages.models.generate)).toHaveLength(1);
expect(findCompletedMessage(values, Generator.messages.models.generate)).toHaveLength(1);

expect(findStartedMessage(values, Generator.messages.inputTypes.title)).toHaveLength(1);
expect(findCompletedMessage(values, Generator.messages.inputTypes.title)).toHaveLength(1);
expect(findStartedMessage(values, Generator.messages.inputTypes.parse)).toHaveLength(1);
expect(findCompletedMessage(values, Generator.messages.inputTypes.parse)).toHaveLength(1);
expect(findStartedMessage(values, Generator.messages.inputTypes.generate)).toHaveLength(1);
expect(findCompletedMessage(values, Generator.messages.inputTypes.generate)).toHaveLength(1);
});
});
28 changes: 27 additions & 1 deletion src/Generator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Listr from "listr";

import { GeneratorConfig } from "../GeneratorConfig";
import { EnumHandler } from "../Handlers/EnumHandler";
import { InputTypeHandler } from "../Handlers/InputTypeHandler";
import { ModelHandler } from "../Handlers/ModelHandler";
import { importDmmf } from "../helpers";

Expand All @@ -17,9 +18,14 @@ export class Generator {
title: "Parsing and generating Enums",
},
init: "Initialize generator",
inputTypes: {
generate: "Generating Input Types",
parse: "Parsing Input Types",
title: "Parsing and generating Input Types",
},
models: {
generate: "Generating Models",
parse: "Parsing Arguments, Models and Object Types",
parse: "Parsing Models",
title: "Parsing and generating Models",
},
objects: "Processing Models, Object Types and Resolvers",
Expand All @@ -32,6 +38,8 @@ export class Generator {

private _enumHandler!: EnumHandler;

private _inputTypeHandler!: InputTypeHandler;

private _modelHandler!: ModelHandler;

constructor({ generator, otherGenerators }: GeneratorOptions) {
Expand All @@ -47,6 +55,7 @@ export class Generator {
title: Generator.messages.init,
},
{
// eslint-disable-next-line max-lines-per-function
task: () =>
new Listr(
[
Expand Down Expand Up @@ -87,6 +96,22 @@ export class Generator {
]),
title: Generator.messages.models.title,
},
{
task: async () =>
new Listr([
{
task: () => this._inputTypeHandler.parse(this._enumHandler.getEnums()),
title: Generator.messages.inputTypes.parse,
},
{
task: async () => {
await this._inputTypeHandler.createFiles();
},
title: Generator.messages.inputTypes.generate,
},
]),
title: Generator.messages.inputTypes.title,
},
],
{ concurrent: true }
),
Expand All @@ -113,6 +138,7 @@ export class Generator {

this._enumHandler = new EnumHandler({ config: this._config, dmmf: this._dmmf });
this._modelHandler = new ModelHandler({ config: this._config, dmmf: this._dmmf });
this._inputTypeHandler = new InputTypeHandler({ config: this._config, dmmf: this._dmmf });

await this._initOutputFolder();
}
Expand Down
4 changes: 4 additions & 0 deletions src/GeneratorConfig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import type { GeneratorConfig as PrismaGeneratorConfig } from "@prisma/generator

interface Paths {
enums: string;
inputTypes: string;
model: string;
shared: string;
}

interface GeneratorOptions {
Expand Down Expand Up @@ -31,7 +33,9 @@ export class GeneratorConfig {
this.basePath = output.value;
this.paths = {
enums: "enums",
inputTypes: "inputTypes",
model: "model",
shared: "shared",
};
this.prismaClientImportPath = prismaClientPath.output.value;
}
Expand Down
46 changes: 45 additions & 1 deletion src/Handlers/BaseHandler/BaseFileGenerator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IndentationText, NewLineKind, Project, StructureKind } from "ts-morph";
import type { GeneratorConfig } from "../../../GeneratorConfig";
import type { Field, ObjectTypes } from "../../../types";
import { NestJSTypes, TypeEnum } from "../../../types";
import type { BaseParser } from "../BaseParser";
import { comparePrimitiveValues } from "../compareFunctions";

interface NestJSImportOptions {
Expand All @@ -12,11 +13,14 @@ interface NestJSImportOptions {
}

export class BaseFileGenerator {
private readonly _baseParser: BaseParser;

private readonly _config: GeneratorConfig;

private readonly _project: Project;

constructor(config: GeneratorConfig) {
constructor(baseParser: BaseParser, config: GeneratorConfig) {
this._baseParser = baseParser;
this._config = config;
this._project = new Project({
compilerOptions: {
Expand All @@ -33,6 +37,10 @@ export class BaseFileGenerator {
addEnumImports({ enums, sourceFile, type }: { enums: string[]; sourceFile: SourceFile; type: TypeEnum }): void {
let moduleSpecifier = "";

if (type === TypeEnum.InputType) {
moduleSpecifier = `../../${this._config.paths.enums}`;
}

if (type === TypeEnum.ModelType) {
moduleSpecifier = `../${this._config.paths.enums}`;
}
Expand All @@ -51,6 +59,42 @@ export class BaseFileGenerator {
});
}

addInputTypeImports({
inputTypes,
model,
sourceFile,
}: {
inputTypes: string[];
model?: string;
sourceFile: SourceFile;
}): void {
inputTypes.forEach((inputType) => {
let moduleSpecifier = "";
const currentModel = this._baseParser.getModelName(inputType);

if (model && model === currentModel) {
moduleSpecifier = `.`;
}

if (model && currentModel && model !== currentModel) {
moduleSpecifier = `../../${currentModel}/${this._config.paths.inputTypes}`;
}

if (!model && !currentModel) {
moduleSpecifier = `.`;
}

if (model && !currentModel) {
moduleSpecifier = `../../${this._config.paths.shared}/${this._config.paths.inputTypes}`;
}

sourceFile.addImportDeclaration({
moduleSpecifier: `${moduleSpecifier}/${inputType}`,
namedImports: [inputType],
});
});
}

// eslint-disable-next-line class-methods-use-this
addJsonImports({ imports, sourceFile }: { imports: string[]; sourceFile: SourceFile }): void {
sourceFile.addImportDeclaration({
Expand Down
111 changes: 64 additions & 47 deletions src/Handlers/BaseHandler/BaseParser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ export class BaseParser {

readonly graphqlScalarImports = new Set(["Byte"]);

readonly inputTypeList: Set<string> = new Set();

readonly jsonImports = new Set(["InputJsonValue", "JsonValue"]);

readonly modelsList: string[];

readonly nestJSImports = new Set([
NestJSTypes.Float as string,
NestJSTypes.GraphQLISODateTime as string,
Expand All @@ -42,6 +46,11 @@ export class BaseParser {

constructor(dmmf: DMMF.Document) {
this.dmmf = dmmf;

this.dmmf.schema.inputObjectTypes.prisma.forEach(({ name }) => {
this.inputTypeList.add(name);
});
this.modelsList = this.dmmf.datamodel.models.map(({ name }) => name);
}

getEnumImports({
Expand Down Expand Up @@ -84,20 +93,61 @@ export class BaseParser {
return graphqlScalarImports;
}

// eslint-disable-next-line class-methods-use-this
getInputType(inputTypes: DMMF.SchemaArgInputType[]): TSField {
if (inputTypes.length === 0) {
/* istanbul ignore next */
throw new Error("No input types available. Extracting GraphQL Type not possible");
}

const isList = inputTypes.find((inputType) => inputType.isList)?.isList ?? false;

let inputTypeObject = inputTypes.find(
(it) => it.location === "inputObjectTypes" || it.location === "outputObjectTypes"
);

if (typeof inputTypeObject === "undefined") {
inputTypeObject = inputTypes.find((it) => it.location === "enumTypes");
}

if (typeof inputTypeObject === "undefined") {
inputTypeObject = inputTypes.find((it) => it.location === "scalar");
}

if (typeof inputTypeObject === "undefined") {
/* istanbul ignore next */
throw new Error("Couldn't parse input type");
}

return {
isList,
location: inputTypeObject.location,
type: inputTypeObject.type as string,
};
}

getJsonImports({
field: { location, type },
jsonImports,
tsType,
}: {
field: {
location: DMMF.FieldLocation;
type: string;
type: DMMF.ArgType;
};
jsonImports: Set<string>;
tsType: string;
}): Set<string> {
if (location === "scalar" && this.jsonImports.has(type)) {
jsonImports.add(type);
let stringType = "";

if (typeof type === "string") {
stringType = type;
} else {
stringType = type.name;
}

if (location === "scalar" && this.jsonImports.has(stringType)) {
jsonImports.add(stringType);
}

if (this.jsonImports.has(tsType.split(" | ")[0])) {
Expand All @@ -107,19 +157,17 @@ export class BaseParser {
return jsonImports;
}

// eslint-disable-next-line class-methods-use-this
getModelImports({
field: { kind, type },
modelImports,
}: {
field: DMMF.Field;
modelImports: Set<string>;
}): Set<string> {
if (kind === "object") {
modelImports.add(type);
}
getModelName(input: string): string | undefined {
// eslint-disable-next-line @typescript-eslint/init-declarations
let modelName: ReturnType<BaseParser["getModelName"]>;

return modelImports;
this.modelsList.forEach((model) => {
if (input.startsWith(model)) {
modelName = model;
}
});

return modelName;
}

parseField({
Expand Down Expand Up @@ -155,7 +203,7 @@ export class BaseParser {

parseGraphQLType(inputTypes: DMMF.SchemaArgInputType[]): string {
let inputType = "";
const inputTypeObject = this._selectInputType(inputTypes);
const inputTypeObject = this.getInputType(inputTypes);
const { isList, location, type } = inputTypeObject;

if (location === "enumTypes") {
Expand Down Expand Up @@ -275,35 +323,4 @@ export class BaseParser {

return fieldType;
}

// eslint-disable-next-line class-methods-use-this
private _selectInputType(inputTypes: DMMF.SchemaArgInputType[]): TSField {
if (inputTypes.length === 0) {
throw new Error("No input types available. Extracting GraphQL Type not possible");
}

const isList = inputTypes.find((inputType) => inputType.isList)?.isList ?? false;

let inputTypeObject = inputTypes.find(
(it) => it.location === "inputObjectTypes" || it.location === "outputObjectTypes"
);

if (typeof inputTypeObject === "undefined") {
inputTypeObject = inputTypes.find((it) => it.location === "enumTypes");
}

if (typeof inputTypeObject === "undefined") {
inputTypeObject = inputTypes.find((it) => it.location === "scalar");
}

if (typeof inputTypeObject === "undefined") {
throw new Error("Couldn't parse input type");
}

return {
isList,
location: inputTypeObject.location,
type: inputTypeObject.type as string,
};
}
}
2 changes: 1 addition & 1 deletion src/Handlers/BaseHandler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ export abstract class BaseHandler {
constructor({ config, dmmf }: HandlerOptions) {
this.config = config;
this.baseParser = new BaseParser(dmmf);
this.baseFileGenerator = new BaseFileGenerator(config);
this.baseFileGenerator = new BaseFileGenerator(this.baseParser, config);
}
}

0 comments on commit 050472d

Please sign in to comment.