@nest-arch/tui 0.2.0 → 0.2.2

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 (52) hide show
  1. package/README.md +7 -0
  2. package/dist/cli.js +1 -1
  3. package/dist/{constants-BbJ71cex.js → constants-DBtqeoTh.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 +8 -1
  7. package/dist/templates/base/README.md.hbs +1 -1
  8. package/dist/templates/base/pnpm-workspace.yaml.hbs +9 -0
  9. package/dist/templates/base/src/app.module.ts.hbs +21 -1
  10. package/dist/templates/base/src/database/db.module.ts.hbs +2 -4
  11. package/dist/templates/base/src/main.ts.hbs +11 -1
  12. package/dist/templates/extras/swagger/create-docs.ts.hbs +81 -25
  13. package/dist/templates/feature/graphql/src/graphql/dto/create-todo.input.ts.hbs +13 -0
  14. package/dist/templates/feature/graphql/src/graphql/dto/update-todo.input.ts.hbs +8 -0
  15. package/dist/templates/feature/graphql/src/graphql/graphql.module.ts.hbs +33 -0
  16. package/dist/templates/feature/graphql/src/graphql/models/base.model.ts.hbs +13 -0
  17. package/dist/templates/feature/graphql/src/graphql/models/pagination.model.ts.hbs +27 -0
  18. package/dist/templates/feature/graphql/src/graphql/models/todo.model.ts.hbs +23 -0
  19. package/dist/templates/feature/graphql/src/graphql/models/user.model.ts.hbs +34 -0
  20. package/dist/templates/feature/graphql/src/graphql/orm/drizzle/drizzle-graphql.module.ts.hbs +42 -0
  21. package/dist/templates/feature/graphql/src/graphql/orm/typeorm/typeorm-graphql-relations.helper.ts.hbs +23 -0
  22. package/dist/templates/feature/graphql/src/graphql/resolvers/base.resolver.ts.hbs +50 -0
  23. package/dist/templates/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +126 -0
  24. package/dist/templates/feature/rest/todo-example/src/api/todo/dto/create-todo.dto.ts.hbs +13 -0
  25. package/dist/templates/feature/rest/todo-example/src/api/todo/dto/update-todo.dto.ts.hbs +13 -0
  26. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.controller.ts.hbs +171 -0
  27. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.module.ts.hbs +24 -0
  28. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.router.ts.hbs +76 -0
  29. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.service.ts.hbs +145 -0
  30. package/dist/templates/monorepo/apps/api-gateway/src/app.module.ts.hbs +21 -1
  31. package/dist/templates/monorepo/apps/api-gateway/src/main.ts.hbs +62 -54
  32. package/dist/templates/monorepo/feature/graphql/src/graphql/dto/create-todo.input.ts.hbs +13 -0
  33. package/dist/templates/monorepo/feature/graphql/src/graphql/dto/update-todo.input.ts.hbs +8 -0
  34. package/dist/templates/monorepo/feature/graphql/src/graphql/graphql.module.ts.hbs +33 -0
  35. package/dist/templates/monorepo/feature/graphql/src/graphql/models/base.model.ts.hbs +13 -0
  36. package/dist/templates/monorepo/feature/graphql/src/graphql/models/todo.model.ts.hbs +23 -0
  37. package/dist/templates/monorepo/feature/graphql/src/graphql/models/user.model.ts.hbs +34 -0
  38. package/dist/templates/monorepo/feature/graphql/src/graphql/orm/drizzle/drizzle-graphql.module.ts.hbs +16 -0
  39. package/dist/templates/monorepo/feature/graphql/src/graphql/orm/prisma/prisma-graphql.service.ts.hbs +9 -0
  40. package/dist/templates/monorepo/feature/graphql/src/graphql/orm/typeorm/typeorm-graphql-relations.helper.ts.hbs +23 -0
  41. package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +122 -0
  42. package/dist/templates/monorepo/packages/env/src/api-gateway.ts.hbs +10 -0
  43. package/dist/templates/monorepo/root/README.md.hbs +1 -1
  44. package/dist/templates/monorepo/root/pnpm-workspace.yaml.hbs +9 -0
  45. package/dist/templates/single-project/architecture/nest-api/.env.hbs +25 -1
  46. package/dist/templates/single-project/architecture/nest-api/pnpm-workspace.yaml.hbs +9 -0
  47. package/dist/templates/single-project/architecture/nest-api/src/main.ts.hbs +67 -55
  48. package/dist/templates/single-project/architecture/nest-gateway/src/main.ts.hbs +62 -54
  49. package/dist/templates/single-project/architecture/nest-microservice/.env.hbs +3 -0
  50. package/dist/templates/single-project/architecture/nest-microservice/src/main.ts.hbs +62 -54
  51. package/dist/templates/single-project/shared/config/env.ts.hbs +10 -0
  52. package/package.json +4 -4
@@ -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=file:./src/database/dev.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
@@ -8,11 +11,15 @@ POSTGRES_URL=postgresql://postgres:postgres@localhost:5432/{{projectNameKebab}}_
8
11
  MYSQL_URL=mysql://root:mysql@localhost:3306/{{projectNameKebab}}_db
9
12
  {{/if}}
10
13
  {{#if hasSqlite}}
11
- SQLITE_PATH=sqlite.db
14
+ SQLITE_PATH=file:./src/database/dev.db
12
15
  {{/if}}
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,16 @@ 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}}
16
+ {{#if hasSqlite}}
17
+ better-sqlite3: true
18
+ {{/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 (or hasRest hasTrpc))}}
26
+ import { TodoModule } from "./api/todo/todo.module.js";
27
+ {{/if}}
28
+ {{#if (and hasTrpc hasTodoExample hasRest)}}
29
+ import { TodoRouter } from "./api/todo/todo.router.js";
30
+ {{/if}}
22
31
  import { AppService } from "./app.service.js";
23
32
 
24
33
  @Module({
25
34
  {{#if hasRest}}
26
- controllers: [AppController],
35
+ controllers: [
36
+ AppController,
37
+ ],
27
38
  {{/if}}
28
39
  imports: [
29
40
  {{#if (or hasDatabase hasOrm)}}
@@ -37,12 +48,18 @@ 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,
43
57
  onError: AppErrorHandler,
44
58
  }),
45
59
  {{/if}}
60
+ {{#if (and hasTodoExample (or hasRest hasTrpc))}}
61
+ TodoModule,
62
+ {{/if}}
46
63
  ],
47
64
  providers: [
48
65
  AppService,
@@ -53,6 +70,9 @@ import { AppService } from "./app.service.js";
53
70
  AppContext,
54
71
  AppErrorHandler,
55
72
  AppRouter,
73
+ {{#if (and hasTrpc hasTodoExample hasRest)}}
74
+ TodoRouter,
75
+ {{/if}}
56
76
  {{/if}}
57
77
  ],
58
78
  })
@@ -14,7 +14,7 @@ import { PrismaSqlAdapter } from './adapters/sql/prisma-sql.adapter.js';
14
14
  import { PrismaPg } from '@prisma/adapter-pg';
15
15
  {{/if}}
16
16
  {{#if hasMysql}}
17
- import { PrismaMySql } from '@prisma/adapter-mysql2';
17
+ import { PrismaMariaDb } from '@prisma/adapter-mariadb';
18
18
  {{/if}}
19
19
  {{#if hasSqlite}}
20
20
  import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
@@ -98,9 +98,7 @@ export class DbModule {
98
98
  const prismaSql = new PrismaClient({ adapter: sqlAdapter });
99
99
  {{/if}}
100
100
  {{#if hasMysql}}
101
- const sqlAdapter = new PrismaMySql({
102
- uri: options.mysqlUrl,
103
- });
101
+ const sqlAdapter = new PrismaMariaDb(options.mysqlUrl!);
104
102
  const prismaSql = new PrismaClient({ adapter: sqlAdapter });
105
103
  {{/if}}
106
104
  {{#if hasSqlite}}
@@ -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}}
@@ -85,6 +89,12 @@ async function bootstrap() {
85
89
  Logger.log(`Swagger documentation available at http://localhost:${env.PORT ?? 3000}${docsPath}`, "SwaggerDocs");
86
90
  {{/if}}
87
91
  await app.listen(env.PORT ?? 3000);
92
+ {{#if hasRest}}
88
93
  Logger.log(`Started backend server on http://localhost:${env.PORT ?? 3000}/api`, "Main");
94
+ {{else if hasTrpc}}
95
+ Logger.log(`Started trpc server on http://localhost:${env.PORT ?? 3000}/trpc`, "Main");
96
+ {{else}}
97
+ Logger.log(`Started server on http://localhost:${env.PORT ?? 3000}`, "Main");
98
+ {{/if}}
89
99
  }
90
100
  await bootstrap();
@@ -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;
@@ -172,7 +208,6 @@ async function applyTrpcSpec(
172
208
  return null;
173
209
  }
174
210
  }
175
-
176
211
  function createEmptyBaseSpec(options: CreateDocsOptions): OpenAPIObject {
177
212
  return {
178
213
  info: {
@@ -190,15 +225,36 @@ export async function createDocs(
190
225
  app: INestApplication,
191
226
  options: CreateDocsOptions = {}
192
227
  ): Promise<OpenAPIObject> {
193
- {{#if hasRest}}
228
+ {{#if (or hasRest hasAuth)}}
194
229
  const config = new DocumentBuilder()
195
230
  .setTitle(options.title ?? "NestJS API")
196
231
  .setDescription(options.description ?? "Generated with Nest Arch")
197
232
  .setVersion(options.version ?? "1.0.0")
198
233
  .build();
199
234
 
200
- let baseSpec = SwaggerModule.createDocument(app, config) as OpenAPIObject;
201
- baseSpec = await applyZodCleanup(baseSpec, options.integrations?.zod);
235
+ const standardSchemaConverter: StandardSchemaConverter = (
236
+ schema,
237
+ { schemaType }
238
+ ) => {
239
+ const standard = (schema as Record<string, unknown> | undefined)?.[
240
+ "~standard"
241
+ ] as Record<string, unknown> | undefined;
242
+ const convert = standard?.jsonSchema?.[schemaType] as
243
+ | ((opts: { target: string }) => unknown)
244
+ | undefined;
245
+ if (!convert) {
246
+ return;
247
+ }
248
+ const converted = convert({ target: "openapi-3.0" });
249
+ if (!converted || typeof converted !== "object") {
250
+ return;
251
+ }
252
+ return normalizeSchema(converted);
253
+ };
254
+
255
+ let baseSpec = SwaggerModule.createDocument(app, config, {
256
+ standardSchemaConverter,
257
+ }) as OpenAPIObject;
202
258
 
203
259
  if (!baseSpec.paths || Object.keys(baseSpec.paths).length === 0) {
204
260
  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
+ }