@nest-arch/tui 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +7 -0
  2. package/dist/cli.js +1 -1
  3. package/dist/{constants-BbJ71cex.js → constants-LZTzRAoQ.js} +1 -1
  4. package/dist/index.js +97 -79
  5. package/dist/templates/base/.env.example.hbs +36 -0
  6. package/dist/templates/base/.env.hbs +7 -0
  7. package/dist/templates/base/README.md.hbs +1 -1
  8. package/dist/templates/base/pnpm-workspace.yaml.hbs +6 -0
  9. package/dist/templates/base/src/app.module.ts.hbs +15 -1
  10. package/dist/templates/base/src/main.ts.hbs +5 -1
  11. package/dist/templates/extras/swagger/create-docs.ts.hbs +80 -36
  12. package/dist/templates/feature/graphql/src/graphql/dto/create-todo.input.ts.hbs +13 -0
  13. package/dist/templates/feature/graphql/src/graphql/dto/update-todo.input.ts.hbs +8 -0
  14. package/dist/templates/feature/graphql/src/graphql/graphql.module.ts.hbs +33 -0
  15. package/dist/templates/feature/graphql/src/graphql/models/base.model.ts.hbs +13 -0
  16. package/dist/templates/feature/graphql/src/graphql/models/pagination.model.ts.hbs +27 -0
  17. package/dist/templates/feature/graphql/src/graphql/models/todo.model.ts.hbs +23 -0
  18. package/dist/templates/feature/graphql/src/graphql/models/user.model.ts.hbs +34 -0
  19. package/dist/templates/feature/graphql/src/graphql/orm/drizzle/drizzle-graphql.module.ts.hbs +42 -0
  20. package/dist/templates/feature/graphql/src/graphql/orm/typeorm/typeorm-graphql-relations.helper.ts.hbs +23 -0
  21. package/dist/templates/feature/graphql/src/graphql/resolvers/base.resolver.ts.hbs +50 -0
  22. package/dist/templates/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +126 -0
  23. package/dist/templates/feature/rest/todo-example/src/api/todo/dto/create-todo.dto.ts.hbs +13 -0
  24. package/dist/templates/feature/rest/todo-example/src/api/todo/dto/update-todo.dto.ts.hbs +13 -0
  25. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.controller.ts.hbs +346 -0
  26. package/dist/templates/monorepo/apps/api-gateway/src/app.module.ts.hbs +15 -1
  27. package/dist/templates/monorepo/apps/api-gateway/src/main.ts.hbs +56 -54
  28. package/dist/templates/monorepo/feature/graphql/src/graphql/dto/create-todo.input.ts.hbs +13 -0
  29. package/dist/templates/monorepo/feature/graphql/src/graphql/dto/update-todo.input.ts.hbs +8 -0
  30. package/dist/templates/monorepo/feature/graphql/src/graphql/graphql.module.ts.hbs +33 -0
  31. package/dist/templates/monorepo/feature/graphql/src/graphql/models/base.model.ts.hbs +13 -0
  32. package/dist/templates/monorepo/feature/graphql/src/graphql/models/todo.model.ts.hbs +23 -0
  33. package/dist/templates/monorepo/feature/graphql/src/graphql/models/user.model.ts.hbs +34 -0
  34. package/dist/templates/monorepo/feature/graphql/src/graphql/orm/drizzle/drizzle-graphql.module.ts.hbs +16 -0
  35. package/dist/templates/monorepo/feature/graphql/src/graphql/orm/prisma/prisma-graphql.service.ts.hbs +9 -0
  36. package/dist/templates/monorepo/feature/graphql/src/graphql/orm/typeorm/typeorm-graphql-relations.helper.ts.hbs +23 -0
  37. package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +122 -0
  38. package/dist/templates/monorepo/packages/env/src/api-gateway.ts.hbs +10 -0
  39. package/dist/templates/monorepo/root/README.md.hbs +1 -1
  40. package/dist/templates/monorepo/root/pnpm-workspace.yaml.hbs +6 -0
  41. package/dist/templates/single-project/architecture/nest-api/.env.hbs +25 -1
  42. package/dist/templates/single-project/architecture/nest-api/pnpm-workspace.yaml.hbs +6 -0
  43. package/dist/templates/single-project/architecture/nest-api/src/main.ts.hbs +61 -55
  44. package/dist/templates/single-project/architecture/nest-gateway/src/main.ts.hbs +56 -54
  45. package/dist/templates/single-project/architecture/nest-microservice/.env.hbs +3 -0
  46. package/dist/templates/single-project/architecture/nest-microservice/src/main.ts.hbs +56 -54
  47. package/dist/templates/single-project/shared/config/env.ts.hbs +10 -0
  48. package/package.json +2 -2
@@ -0,0 +1,36 @@
1
+ # Server
2
+ PORT=3000
3
+ NODE_ENV=development
4
+ CORS_ORIGIN=http://localhost:3001
5
+
6
+ {{#if hasDatabase}}
7
+ # Database
8
+ {{/if}}
9
+ {{#if hasPostgres}}
10
+ POSTGRES_URL=postgresql://postgres:postgres@localhost:5432/{{projectNameKebab}}_db?schema=public
11
+ {{/if}}
12
+ {{#if hasMysql}}
13
+ MYSQL_URL=mysql://root:mysql@localhost:3306/{{projectNameKebab}}_db
14
+ {{/if}}
15
+ {{#if hasSqlite}}
16
+ SQLITE_PATH=sqlite.db
17
+ {{/if}}
18
+ {{#if hasMongodb}}
19
+ MONGODB_URL=mongodb://localhost:27017/{{projectNameKebab}}_db
20
+ {{/if}}
21
+
22
+ {{#if hasGraphql}}
23
+ # GraphQL
24
+ GRAPHQL_PLAYGROUND=true
25
+
26
+ {{/if}}
27
+ {{#if (and (eq auth "passport") hasDatabase)}}
28
+ # JWT
29
+ JWT_ACCESS_SECRET=change-me-to-a-random-secret-at-least-32-characters-long
30
+ JWT_REFRESH_SECRET=change-me-to-a-different-random-secret-at-least-32-characters
31
+ {{/if}}
32
+ {{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
33
+ # Better Auth
34
+ BETTER_AUTH_SECRET=change-me-to-a-random-secret-at-least-32-characters-long
35
+ BETTER_AUTH_URL=http://localhost:3000
36
+ {{/if}}
@@ -1,5 +1,8 @@
1
1
  CORS_ORIGIN=http://localhost:3001
2
2
  PORT=3000
3
+ {{#if hasGraphql}}
4
+ GRAPHQL_PLAYGROUND=true
5
+ {{/if}}
3
6
 
4
7
  {{#if hasPostgres}}
5
8
  POSTGRES_URL=postgresql://postgres:postgres@localhost:5432/{{projectNameKebab}}_db?schema=public
@@ -13,6 +16,10 @@ SQLITE_PATH=sqlite.db
13
16
  {{#if hasMongodb}}
14
17
  MONGODB_URL=mongodb://localhost:27017/{{projectNameKebab}}_db
15
18
  {{/if}}
19
+ {{#if (and (eq auth "passport") hasDatabase)}}
20
+ JWT_ACCESS_SECRET=change-me-to-a-random-secret-at-least-32-characters-long
21
+ JWT_REFRESH_SECRET=change-me-to-a-different-random-secret-at-least-32-characters
22
+ {{/if}}
16
23
  {{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
17
24
  BETTER_AUTH_SECRET=change-me-to-a-random-secret-at-least-32-characters-long
18
25
  BETTER_AUTH_URL=http://localhost:3000
@@ -82,7 +82,7 @@ This project uses **TypeORM** as the ORM. Available commands:
82
82
  This project uses **Better Auth**. Generate the database schema by running:
83
83
 
84
84
  ```bash
85
- {{#if isPnpm}}pnpm dlx auth@latest generate --config src/auth/auth.ts --output src/database/orm/prisma/schema.prisma -y{{else if isYarn}}yarn dlx auth@latest generate --config src/auth/auth.ts --output src/database/orm/prisma/schema.prisma -y{{else if isBun}}bun x auth@latest generate --config src/auth/auth.ts --output src/database/orm/prisma/schema.prisma -y{{else}}npx auth@latest generate --config src/auth/auth.ts --output src/database/orm/prisma/schema.prisma -y{{/if}}
85
+ {{#if isPnpm}}pnpm dlx auth@latest generate --config src/auth/auth.ts --output src/database/orm/prisma/schema.prisma -y && pnpm run prisma:format{{else if isYarn}}yarn dlx auth@latest generate --config src/auth/auth.ts --output src/database/orm/prisma/schema.prisma -y && yarn run prisma:format{{else if isBun}}bun x auth@latest generate --config src/auth/auth.ts --output src/database/orm/prisma/schema.prisma -y && bun run prisma:format{{else}}npx auth@latest generate --config src/auth/auth.ts --output src/database/orm/prisma/schema.prisma -y && npm run prisma:format{{/if}}
86
86
  ```
87
87
  See https://better-auth.com/docs/adapters/prisma for more details.
88
88
 
@@ -3,7 +3,13 @@ allowBuilds:
3
3
  {{#if swagger}}
4
4
  "@scarf/scarf": true
5
5
  {{/if}}
6
+ {{#if hasGraphql}}
7
+ "@apollo/protobufjs": true
8
+ {{/if}}
6
9
  {{#if usePrisma}}
7
10
  prisma: true
8
11
  "@prisma/engines": true
9
12
  {{/if}}
13
+ {{#if (and (eq auth "passport") hasDatabase)}}
14
+ bcrypt: true
15
+ {{/if}}
@@ -6,6 +6,9 @@ import { DbModule } from "./database/db.module.js";
6
6
  {{#if shouldImportAuthModule}}
7
7
  import { AuthModule } from "./auth/auth.module.js";
8
8
  {{/if}}
9
+ {{#if hasGraphql}}
10
+ import { GraphqlAppModule } from "./graphql/graphql.module.js";
11
+ {{/if}}
9
12
  {{#if hasTrpc}}
10
13
  import { TRPCModule } from "nestjs-trpc";
11
14
  import { AppRouterClass as AppRouter } from "./app.router.js";
@@ -19,11 +22,19 @@ import { AuthTRPCMiddleware } from "./libs/middlewares/auth.trpc.middleware.js";
19
22
  {{#if hasRest}}
20
23
  import { AppController } from "./app.controller.js";
21
24
  {{/if}}
25
+ {{#if (and hasTodoExample hasRest)}}
26
+ import { TodoController } from "./api/todo/todo.controller.js";
27
+ {{/if}}
22
28
  import { AppService } from "./app.service.js";
23
29
 
24
30
  @Module({
25
31
  {{#if hasRest}}
26
- controllers: [AppController],
32
+ controllers: [
33
+ AppController,
34
+ {{#if (and hasTodoExample hasRest)}}
35
+ TodoController,
36
+ {{/if}}
37
+ ],
27
38
  {{/if}}
28
39
  imports: [
29
40
  {{#if (or hasDatabase hasOrm)}}
@@ -37,6 +48,9 @@ import { AppService } from "./app.service.js";
37
48
  {{#if shouldImportAuthModule}}
38
49
  AuthModule,
39
50
  {{/if}}
51
+ {{#if hasGraphql}}
52
+ GraphqlAppModule,
53
+ {{/if}}
40
54
  {{#if hasTrpc}}
41
55
  TRPCModule.forRoot({
42
56
  context: AppContext,
@@ -2,7 +2,7 @@ import { NestFactory } from "@nestjs/core";
2
2
  {{#if (eq httpProvider "fastify")}}
3
3
  import { FastifyAdapter, NestFastifyApplication } from "@nestjs/platform-fastify";
4
4
  {{/if}}
5
- import { Logger } from "@nestjs/common";
5
+ import { Logger, StandardSchemaValidationPipe } from "@nestjs/common";
6
6
 
7
7
  import { AppModule } from "./app.module.js";
8
8
  import { env } from "./config/env.js";
@@ -24,7 +24,11 @@ async function bootstrap() {
24
24
  app.enableCors({
25
25
  origin: env.CORS_ORIGIN,
26
26
  });
27
+ {{#if hasRest}}
27
28
  app.setGlobalPrefix("api");
29
+ {{/if}}
30
+ app.useGlobalPipes(new StandardSchemaValidationPipe());
31
+ app.enableShutdownHooks();
28
32
  {{#if swagger}}
29
33
  const docsPath = "/docs";
30
34
  {{#if useSwaggerScalar}}
@@ -40,6 +40,58 @@ export interface CreateDocsOptions {
40
40
  integrations?: DocsIntegrations;
41
41
  }
42
42
 
43
+ function rewriteRefs(value: unknown): unknown {
44
+ if (Array.isArray(value)) {
45
+ return value.map((item) => rewriteRefs(item));
46
+ }
47
+ if (!value || typeof value !== "object") {
48
+ return value;
49
+ }
50
+ const obj = value as Record<string, unknown>;
51
+ if (typeof obj.$ref === "string") {
52
+ const rest = rewriteRefs(
53
+ Object.fromEntries(Object.entries(obj).filter(([k]) => k !== "$ref"))
54
+ ) as Record<string, unknown>;
55
+ return {
56
+ ...rest,
57
+ $ref: (obj.$ref as string)
58
+ .replace("#/$defs/", "#/components/schemas/")
59
+ .replace("#/definitions/", "#/components/schemas/"),
60
+ };
61
+ }
62
+ return Object.fromEntries(
63
+ Object.entries(obj).map(([k, v]) => [k, rewriteRefs(v)])
64
+ );
65
+ }
66
+
67
+ function normalizeSchema(
68
+ schema: unknown,
69
+ components: Record<string, unknown> = {}
70
+ ): ReturnType<StandardSchemaConverter> {
71
+ const raw = (
72
+ typeof schema === "object" && schema !== null ? schema : {}
73
+ ) as Record<string, unknown>;
74
+ const normalizedComponents = Object.fromEntries(
75
+ Object.entries(raw.$defs ?? {}).map(([name, def]) => [
76
+ name,
77
+ rewriteRefs(def),
78
+ ])
79
+ );
80
+ const filtered: Record<string, unknown> = {};
81
+ for (const key of Object.keys(raw)) {
82
+ if (key !== "$defs" && key !== "definitions" && key !== "$schema") {
83
+ filtered[key] = raw[key];
84
+ }
85
+ }
86
+ return {
87
+ components: {
88
+ ...components,
89
+ ...normalizedComponents,
90
+ },
91
+ schema: rewriteRefs(filtered),
92
+ };
93
+ }
94
+
43
95
  function addPrefixToPaths(paths: Record<string, unknown>, prefix: string) {
44
96
  return Object.fromEntries(
45
97
  Object.entries(paths).map(([path, value]) => [`${prefix}${path}`, value])
@@ -79,26 +131,6 @@ function mergeOpenApiSpecs(baseSpec: OpenAPIObject, ...extraSpecs: OpenAPIObject
79
131
  } as OpenAPIObject;
80
132
  }
81
133
 
82
- async function applyZodCleanup(
83
- baseSpec: OpenAPIObject,
84
- options?: NonNullable<DocsIntegrations["zod"]>
85
- ) {
86
- try {
87
- const { cleanupOpenApiDoc } = await import("nestjs-zod");
88
- if (typeof cleanupOpenApiDoc !== "function") {
89
- return baseSpec;
90
- }
91
-
92
- if (options?.withFastify === true) {
93
- return cleanupOpenApiDoc(baseSpec, { withFastify: true }) as OpenAPIObject;
94
- }
95
-
96
- return cleanupOpenApiDoc(baseSpec) as OpenAPIObject;
97
- } catch {
98
- return baseSpec;
99
- }
100
- }
101
-
102
134
  async function applyBetterAuthSpec(
103
135
  options?: NonNullable<DocsIntegrations["betterAuth"]>
104
136
  ) {
@@ -145,7 +177,11 @@ async function applyTrpcSpec(
145
177
  }
146
178
 
147
179
  try {
148
- const { generateOpenAPIDocument } = await import("@trpc/openapi");
180
+ const specifier = "@trpc/openapi" as string;
181
+ const mod: Record<string, unknown> = await import(specifier);
182
+ const generateOpenAPIDocument = mod.generateOpenAPIDocument as
183
+ | ((opts: unknown, config: unknown) => unknown)
184
+ | undefined;
149
185
 
150
186
  if (typeof generateOpenAPIDocument !== "function") {
151
187
  return null;
@@ -173,19 +209,6 @@ async function applyTrpcSpec(
173
209
  }
174
210
  }
175
211
 
176
- function createEmptyBaseSpec(options: CreateDocsOptions): OpenAPIObject {
177
- return {
178
- info: {
179
- title: options.title ?? "NestJS API",
180
- description: options.description ?? "Generated with Nest Arch",
181
- version: options.version ?? "1.0.0",
182
- },
183
- openapi: "3.1.0",
184
- paths: {},
185
- tags: [],
186
- } as OpenAPIObject;
187
- }
188
-
189
212
  export async function createDocs(
190
213
  app: INestApplication,
191
214
  options: CreateDocsOptions = {}
@@ -197,8 +220,29 @@ export async function createDocs(
197
220
  .setVersion(options.version ?? "1.0.0")
198
221
  .build();
199
222
 
200
- let baseSpec = SwaggerModule.createDocument(app, config) as OpenAPIObject;
201
- baseSpec = await applyZodCleanup(baseSpec, options.integrations?.zod);
223
+ const standardSchemaConverter: StandardSchemaConverter = (
224
+ schema,
225
+ { schemaType }
226
+ ) => {
227
+ const standard = (schema as Record<string, unknown> | undefined)?.[
228
+ "~standard"
229
+ ] as Record<string, unknown> | undefined;
230
+ const convert = standard?.jsonSchema?.[schemaType] as
231
+ | ((opts: { target: string }) => unknown)
232
+ | undefined;
233
+ if (!convert) {
234
+ return;
235
+ }
236
+ const converted = convert({ target: "openapi-3.0" });
237
+ if (!converted || typeof converted !== "object") {
238
+ return;
239
+ }
240
+ return normalizeSchema(converted);
241
+ };
242
+
243
+ let baseSpec = SwaggerModule.createDocument(app, config, {
244
+ standardSchemaConverter,
245
+ }) as OpenAPIObject;
202
246
 
203
247
  if (!baseSpec.paths || Object.keys(baseSpec.paths).length === 0) {
204
248
  baseSpec = {
@@ -0,0 +1,13 @@
1
+ import { Field, InputType } from "@nestjs/graphql";
2
+
3
+ @InputType()
4
+ export class CreateTodoInput {
5
+ @Field()
6
+ title: string;
7
+
8
+ @Field({ nullable: true })
9
+ description?: string;
10
+
11
+ @Field({ defaultValue: false })
12
+ completed: boolean;
13
+ }
@@ -0,0 +1,8 @@
1
+ import { Field, ID, InputType, PartialType } from "@nestjs/graphql";
2
+ import { CreateTodoInput } from "./create-todo.input.js";
3
+
4
+ @InputType()
5
+ export class UpdateTodoInput extends PartialType(CreateTodoInput) {
6
+ @Field(() => ID)
7
+ id: string;
8
+ }
@@ -0,0 +1,33 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { GraphQLModule } from "@nestjs/graphql";
3
+ import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
4
+ import { join } from "path";
5
+ import { env } from "../config/env.js";
6
+ {{#if (eq httpProvider "fastify")}}
7
+ // WARNING: If you use @as-integrations/fastify with multiple GraphQL endpoints
8
+ // in a single application, make sure to enable the disableHealthCheck setting
9
+ // in the GraphQLModule configuration below.
10
+ {{/if}}
11
+ {{#if hasTodoExample}}
12
+ import { TodoResolver } from "./resolvers/todo.resolver.js";
13
+ {{/if}}
14
+
15
+ @Module({
16
+ imports: [
17
+ GraphQLModule.forRoot<ApolloDriverConfig>({
18
+ driver: ApolloDriver,
19
+ autoSchemaFile: join(process.cwd(), "src/schema.gql"),
20
+ graphiql: env.GRAPHQL_PLAYGROUND,
21
+ sortSchema: true,
22
+ {{#if (eq httpProvider "fastify")}}
23
+ disableHealthCheck: true,
24
+ {{/if}}
25
+ }),
26
+ ],
27
+ providers: [
28
+ {{#if hasTodoExample}}
29
+ TodoResolver,
30
+ {{/if}}
31
+ ],
32
+ })
33
+ export class GraphqlAppModule {}
@@ -0,0 +1,13 @@
1
+ import { Field, ID, ObjectType } from "@nestjs/graphql";
2
+
3
+ @ObjectType({ isAbstract: true })
4
+ export abstract class BaseModel {
5
+ @Field(() => ID)
6
+ id: string;
7
+
8
+ @Field()
9
+ createdAt: Date;
10
+
11
+ @Field()
12
+ updatedAt: Date;
13
+ }
@@ -0,0 +1,27 @@
1
+ import { Field, Int, ObjectType } from "@nestjs/graphql";
2
+
3
+ @ObjectType()
4
+ export class PageInfo {
5
+ @Field()
6
+ hasNextPage: boolean;
7
+
8
+ @Field()
9
+ hasPreviousPage: boolean;
10
+
11
+ @Field({ nullable: true })
12
+ startCursor?: string;
13
+
14
+ @Field({ nullable: true })
15
+ endCursor?: string;
16
+ }
17
+
18
+ @ObjectType()
19
+ export class Paginated<T> {
20
+ nodes: T[];
21
+
22
+ @Field(() => Int)
23
+ totalCount: number;
24
+
25
+ @Field()
26
+ pageInfo: PageInfo;
27
+ }
@@ -0,0 +1,23 @@
1
+ import { Field, ID, ObjectType } from "@nestjs/graphql";
2
+
3
+ import { BaseModel } from "./base.model.js";
4
+ // oxlint-disable-next-line import/no-cycle
5
+ import { User } from "./user.model.js";
6
+
7
+ @ObjectType()
8
+ export class Todo extends BaseModel {
9
+ @Field()
10
+ title: string;
11
+
12
+ @Field(() => String, { nullable: true })
13
+ description: string | null;
14
+
15
+ @Field()
16
+ completed: boolean;
17
+
18
+ @Field(() => ID, { nullable: true })
19
+ authorId: string | null;
20
+
21
+ @Field(() => User, { nullable: true })
22
+ author: User | null;
23
+ }
@@ -0,0 +1,34 @@
1
+ import { Field, ObjectType, registerEnumType } from "@nestjs/graphql";
2
+
3
+ import { BaseModel } from "./base.model.js";
4
+ {{#if hasTodoExample}}
5
+ // oxlint-disable-next-line import/no-cycle
6
+ import { Todo } from "./todo.model.js";
7
+ {{/if}}
8
+
9
+ export enum Role {
10
+ ADMIN = "ADMIN",
11
+ USER = "USER",
12
+ }
13
+
14
+ registerEnumType(Role, { name: "Role" });
15
+
16
+ @ObjectType()
17
+ export class User extends BaseModel {
18
+ @Field()
19
+ email: string;
20
+
21
+ @Field(() => String, { nullable: true })
22
+ firstname: string | null;
23
+
24
+ @Field(() => String, { nullable: true })
25
+ lastname: string | null;
26
+
27
+ @Field(() => Role)
28
+ role: Role;
29
+ {{#if hasTodoExample}}
30
+
31
+ @Field(() => [Todo], { nullable: "items" })
32
+ todos: Todo[];
33
+ {{/if}}
34
+ }
@@ -0,0 +1,42 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { buildSchema } from "drizzle-graphql";
3
+ {{#if isMonorepo}}
4
+ import { db } from "@repo/db";
5
+ {{else}}
6
+ {{#if hasPostgres}}
7
+ import { drizzle } from "drizzle-orm/node-postgres";
8
+ import { Pool } from "pg";
9
+ {{/if}}
10
+ {{#if hasMysql}}
11
+ import { drizzle } from "drizzle-orm/mysql2";
12
+ import mysql from "mysql2";
13
+ {{/if}}
14
+ {{#if hasSqlite}}
15
+ import { drizzle } from "drizzle-orm/better-sqlite3";
16
+ import Database from "better-sqlite3";
17
+ {{/if}}
18
+ import * as dbSchema from "../database/orm/drizzle/schema.js";
19
+ {{/if}}
20
+
21
+ const schema = buildSchema({{#if isMonorepo}}db{{else}}
22
+ {{#if hasPostgres}}
23
+ drizzle(new Pool({ connectionString: process.env.DATABASE_URL }), { schema: dbSchema })
24
+ {{/if}}
25
+ {{#if hasMysql}}
26
+ drizzle(mysql.createPool(process.env.DATABASE_URL), { schema: dbSchema })
27
+ {{/if}}
28
+ {{#if hasSqlite}}
29
+ drizzle(new Database(process.env.DATABASE_URL), { schema: dbSchema })
30
+ {{/if}}
31
+ {{/if}});
32
+
33
+ @Module({
34
+ providers: [
35
+ {
36
+ provide: "DRIZZLE_GRAPHQL_SCHEMA",
37
+ useValue: schema,
38
+ },
39
+ ],
40
+ exports: ["DRIZZLE_GRAPHQL_SCHEMA"],
41
+ })
42
+ export class DrizzleGraphqlModule {}
@@ -0,0 +1,23 @@
1
+ import { GraphQLResolveInfo } from "graphql";
2
+ import { DataSource, EntityTarget, FindOptionsRelations } from "typeorm";
3
+ import { GraphRelationBuilder } from "typeorm-relations-graphql";
4
+
5
+ export class TypeOrmGraphqlRelationsHelper {
6
+ private readonly builder: GraphRelationBuilder;
7
+
8
+ constructor(dataSource: DataSource) {
9
+ this.builder = new GraphRelationBuilder(dataSource);
10
+ }
11
+
12
+ resolve<T>(
13
+ entity: EntityTarget<T>,
14
+ info: GraphQLResolveInfo,
15
+ path?: string
16
+ ): FindOptionsRelations<T> {
17
+ const relationMap = path
18
+ ? this.builder.buildForQuery(entity, info, path)
19
+ : this.builder.buildForQuery(entity, info);
20
+
21
+ return relationMap as unknown as FindOptionsRelations<T>;
22
+ }
23
+ }
@@ -0,0 +1,50 @@
1
+ import { Args, ID, Mutation, Query } from "@nestjs/graphql";
2
+ {{#if useTypeorm}}
3
+ import { GraphRelationBuilder } from "typeorm-relations-graphql";
4
+ import { GraphQLResolveInfo } from "graphql";
5
+ {{/if}}
6
+
7
+ export abstract class BaseResolver<T, C, U> {
8
+ protected abstract get dbService(): unknown;
9
+
10
+ @Query(() => [Object])
11
+ async findAll({{#if useTypeorm}}info: GraphQLResolveInfo{{/if}}): Promise<T[]> {
12
+ {{#if useTypeorm}}
13
+ const builder = new GraphRelationBuilder(this.dbService.getDataSource());
14
+ const relations = builder.buildForQuery(this.getEntityClass(), info);
15
+ return this.dbService.getRepository(this.getEntityClass()).find({ relations });
16
+ {{else}}
17
+ return [];
18
+ {{/if}}
19
+ }
20
+
21
+ @Mutation(() => Object)
22
+ async create(
23
+ @Args("input") input: C{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}
24
+ ): Promise<T> {
25
+ {{#if useTypeorm}}
26
+ const entity = this.dbService.getRepository(this.getEntityClass()).create(input);
27
+ const saved = await this.dbService.getRepository(this.getEntityClass()).save(entity);
28
+ const builder = new GraphRelationBuilder(this.dbService.getDataSource());
29
+ const relations = builder.buildForQuery(this.getEntityClass(), info);
30
+ return this.dbService.getRepository(this.getEntityClass()).findOneOrFail({
31
+ where: { id: saved.id },
32
+ relations,
33
+ });
34
+ {{else}}
35
+ return input as unknown as T;
36
+ {{/if}}
37
+ }
38
+
39
+ @Mutation(() => Boolean)
40
+ async delete(@Args("id", { type: () => ID }) id: string): Promise<boolean> {
41
+ {{#if useTypeorm}}
42
+ const result = await this.dbService.getRepository(this.getEntityClass()).delete(id);
43
+ return (result.affected ?? 0) > 0;
44
+ {{else}}
45
+ return true;
46
+ {{/if}}
47
+ }
48
+
49
+ protected abstract getEntityClass(): new () => T;
50
+ }