Skip to content

Commit

Permalink
feat: add support for resolver generation
Browse files Browse the repository at this point in the history
  • Loading branch information
rfermann committed Apr 22, 2021
1 parent fbf4656 commit 5011af4
Show file tree
Hide file tree
Showing 16 changed files with 946 additions and 82 deletions.
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,28 @@ To use NestJS-Prisma-GraphQL-Generator, follow these steps:

## Configuration

Create a new generator in your prisma schema:
Create a new generator in your prisma schema and provide the import path of your prisma service:

```
generator nestjs {
provider = "nestjs-prisma-graphql"
provider = "nestjs-prisma-graphql"
prismaServiceImportPath = "@your/prisma-service"
}
```

The following additional settings can be configured within the generator block of your prisma schema:

- the input arguments name; default setting:
`inputArgumentsName = input`
- the named prisma service import; default setting:
`prismaServiceImport = PrismaService`

To enable the [Prisma Select](https://paljs.com/plugins/select/) integration, set `includePrismaSelect` to `true`:

```
generator nestjs {
provider = "nestjs-prisma-graphql"
includePrismaSelect = true
}
```

Expand All @@ -49,15 +66,18 @@ generator nestjs {
- Models
- Input Types
- Output Types
- Resolvers

## Usage

All supported objects can be used as the regular NestJS GraphQL object equivalents.

Resolvers can be imported on an individual base and used within the `providers` array of your modules

## Upcoming features

- Resolvers
- [Prisma Select](https://paljs.com/plugins/select/) integration
- barrel files for easier imports
- support for adding custom decorators to the generated objects

## Contributing to NestJS-Prisma-GraphQL-Generator

Expand Down
15 changes: 13 additions & 2 deletions src/Generator/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ const findCompletedMessage = (values: string[], message: string) =>

const generatorConfig: GeneratorConfig = {
binaryTargets: [],
config: {},
config: {
prismaServiceImportPath: "nestjs-prisma",
},
name: "client",
output: {
fromEnvVar: null,
Expand Down Expand Up @@ -81,7 +83,7 @@ describe("Generator", () => {
expect([...folderList]).toStrictEqual(["enums", "Session", "shared", "User"]);
});
it("should log the correct actions", async () => {
expect.assertions(29);
expect.assertions(36);

const generator = new Generator({
datamodel: "",
Expand All @@ -108,6 +110,8 @@ describe("Generator", () => {

consoleLog.mockRestore();

expect(values).toHaveLength(36);

// expect starting and closing messages to appear in the correct order
expect(values[0]).toMatch(getStartedMessage(Generator.messages.init));
expect(values[1]).toMatch(getCompletedMessage(Generator.messages.init));
Expand Down Expand Up @@ -144,5 +148,12 @@ describe("Generator", () => {
expect(findCompletedMessage(values, Generator.messages.outputTypes.parse)).toHaveLength(1);
expect(findStartedMessage(values, Generator.messages.outputTypes.generate)).toHaveLength(1);
expect(findCompletedMessage(values, Generator.messages.outputTypes.generate)).toHaveLength(1);

expect(findStartedMessage(values, Generator.messages.resolvers.title)).toHaveLength(1);
expect(findCompletedMessage(values, Generator.messages.resolvers.title)).toHaveLength(1);
expect(findStartedMessage(values, Generator.messages.resolvers.parse)).toHaveLength(1);
expect(findCompletedMessage(values, Generator.messages.resolvers.parse)).toHaveLength(1);
expect(findStartedMessage(values, Generator.messages.resolvers.generate)).toHaveLength(1);
expect(findCompletedMessage(values, Generator.messages.resolvers.generate)).toHaveLength(1);
});
});
25 changes: 25 additions & 0 deletions src/Generator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { EnumHandler } from "../Handlers/EnumHandler";
import { InputTypeHandler } from "../Handlers/InputTypeHandler";
import { ModelHandler } from "../Handlers/ModelHandler";
import { OutputTypeHandler } from "../Handlers/OutputTypeHandler";
import { ResolverHandler } from "../Handlers/ResolverHandler";
import { importDmmf } from "../helpers";

export class Generator {
Expand All @@ -35,6 +36,11 @@ export class Generator {
parse: "Parsing Output Types",
title: "Parsing and generating Output Types",
},
resolvers: {
generate: "Generating Resolvers",
parse: "Parsing Resolvers",
title: "Parsing and generating Resolvers",
},
title: "Generating NestJS integration",
};

Expand All @@ -50,6 +56,8 @@ export class Generator {

private _outputTypeHandler!: OutputTypeHandler;

private _resolverHandler!: ResolverHandler;

constructor({ generator, otherGenerators }: GeneratorOptions) {
this._config = new GeneratorConfig({ generator, otherGenerators });
}
Expand Down Expand Up @@ -137,6 +145,22 @@ export class Generator {
]),
title: Generator.messages.outputTypes.title,
},
{
task: async () =>
new Listr([
{
task: () => this._resolverHandler.parse(this._enumHandler.getEnums()),
title: Generator.messages.resolvers.parse,
},
{
task: async () => {
await this._resolverHandler.createFiles();
},
title: Generator.messages.resolvers.generate,
},
]),
title: Generator.messages.resolvers.title,
},
],
{ concurrent: true }
),
Expand Down Expand Up @@ -165,6 +189,7 @@ export class Generator {
this._modelHandler = new ModelHandler({ config: this._config, dmmf: this._dmmf });
this._inputTypeHandler = new InputTypeHandler({ config: this._config, dmmf: this._dmmf });
this._outputTypeHandler = new OutputTypeHandler({ config: this._config, dmmf: this._dmmf });
this._resolverHandler = new ResolverHandler({ config: this._config, dmmf: this._dmmf });

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

const generatorConfig: PrismaGeneratorConfig = {
binaryTargets: [],
config: {},
config: {
prismaServiceImportPath: "nestjs-prisma",
},
name: "client",
output: {
fromEnvVar: null,
Expand All @@ -16,7 +18,9 @@ const generatorConfig: PrismaGeneratorConfig = {

const prismaGenerator: PrismaGeneratorConfig = {
binaryTargets: [],
config: {},
config: {
prismaServiceImportPath: "nestjs-prisma",
},
name: "client",
output: {
fromEnvVar: null,
Expand Down
17 changes: 16 additions & 1 deletion src/GeneratorConfig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface Paths {
inputTypes: string;
model: string;
outputTypes: string;
resolvers: string;
shared: string;
}

Expand All @@ -16,12 +17,18 @@ interface GeneratorOptions {
export class GeneratorConfig {
readonly basePath: string;

readonly includePrismaSelect: boolean;

readonly inputArgumentsName: string;

readonly paths: Paths;

readonly prismaClientImportPath: string;

readonly prismaServiceImport: string;

readonly prismaServiceImportPath: string;

constructor({ generator: { config, output }, otherGenerators }: GeneratorOptions) {
if (output === null) {
throw new Error("Please define an output directory");
Expand All @@ -33,15 +40,23 @@ export class GeneratorConfig {
throw new Error("Cannot detect prisma client. Please make sure to generate `prisma-client-js` first");
}

if (!config.prismaServiceImportPath || config.prismaServiceImportPath.length < 1) {
throw new Error("Cannot detect prisma service. Please make sure to provide a path to your prisma service");
}

this.basePath = output.value;
this.paths = {
enums: "enums",
inputTypes: "inputTypes",
model: "model",
outputTypes: "outputTypes",
resolvers: "resolvers",
shared: "shared",
};
this.prismaClientImportPath = prismaClientPath.output.value;
this.includePrismaSelect = config.includePrismaSelect === "true" || false;
this.inputArgumentsName = config.inputArgumentsName || "input";
this.prismaClientImportPath = prismaClientPath.output.value;
this.prismaServiceImport = config.prismaServiceImport || "PrismaService";
this.prismaServiceImportPath = config.prismaServiceImportPath;
}
}
55 changes: 40 additions & 15 deletions src/Handlers/BaseHandler/BaseFileGenerator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,17 @@ export class BaseFileGenerator {
});
}

addInputTypeImports({ sourceFile, types, model }: { model?: string; sourceFile: SourceFile; types: string[] }): void {
addInputTypeImports({
isResolver = false,
model,
sourceFile,
types,
}: {
isResolver?: boolean;
model?: string;
sourceFile: SourceFile;
types: string[];
}): void {
types.sort(comparePrimitiveValues).forEach((type) => {
let moduleSpecifier = "";
const currentModel = this._baseParser.getModelName(type);
Expand All @@ -80,6 +90,10 @@ export class BaseFileGenerator {
moduleSpecifier = `../../${this._config.paths.shared}/${this._config.paths.inputTypes}`;
}

if (isResolver) {
moduleSpecifier = `../${this._config.paths.inputTypes}`;
}

sourceFile.addImportDeclaration({
moduleSpecifier: `${moduleSpecifier}/${type}`,
namedImports: [type],
Expand Down Expand Up @@ -108,6 +122,10 @@ export class BaseFileGenerator {
moduleSpecifier = `../${model}/model`;
}

if (type === TypeEnum.Resolver) {
moduleSpecifier = `../../${model}/model`;
}

sourceFile.addImportDeclaration({
moduleSpecifier,
namedImports: [model],
Expand Down Expand Up @@ -136,33 +154,35 @@ export class BaseFileGenerator {
}

addOutputTypeImports({
isResolver = false,
sourceFile,
types,
model,
}: {
isResolver?: boolean;
model?: string;
sourceFile: SourceFile;
types: string[];
}): void {
types.sort(comparePrimitiveValues).forEach((type) => {
if (type === model) {
return;
}

let moduleSpecifier = "";
const currentModel = this._baseParser.getModelName(type);

if (model && model === currentModel) {
moduleSpecifier = `.`;
if (isResolver) {
moduleSpecifier = `../${this._config.paths.outputTypes}`;
} else {
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}`;
// }
if (model && !currentModel) {
moduleSpecifier = `../../${this._config.paths.shared}/${this._config.paths.outputTypes}`;
}

sourceFile.addImportDeclaration({
moduleSpecifier: `${moduleSpecifier}/${type}`,
Expand All @@ -181,17 +201,22 @@ export class BaseFileGenerator {
getClassDecorator({
decoratorType,
documentation,
isAbstract = true,
returnType,
}: {
decoratorType: ObjectTypes;
documentation?: string;
isAbstract?: boolean;
returnType?: string;
}): OptionalKind<DecoratorStructure>[] {
return [
{
arguments: [
(writer) =>
writer
.write("{")
.writeLine("isAbstract: true,")
.conditionalWriteLine(typeof returnType !== "string", "{")
.conditionalWriteLine(typeof returnType === "string", `() => ${returnType}, {`)
.writeLine(`isAbstract: ${isAbstract},`)
.conditionalWriteLine(typeof documentation === "string", `description: "${documentation}"`)
.write("}"),
],
Expand Down

0 comments on commit 5011af4

Please sign in to comment.