@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.
- package/README.md +7 -0
- package/dist/cli.js +1 -1
- package/dist/{constants-BbJ71cex.js → constants-LZTzRAoQ.js} +1 -1
- package/dist/index.js +97 -79
- package/dist/templates/base/.env.example.hbs +36 -0
- package/dist/templates/base/.env.hbs +7 -0
- package/dist/templates/base/README.md.hbs +1 -1
- package/dist/templates/base/pnpm-workspace.yaml.hbs +6 -0
- package/dist/templates/base/src/app.module.ts.hbs +15 -1
- package/dist/templates/base/src/main.ts.hbs +5 -1
- package/dist/templates/extras/swagger/create-docs.ts.hbs +80 -36
- package/dist/templates/feature/graphql/src/graphql/dto/create-todo.input.ts.hbs +13 -0
- package/dist/templates/feature/graphql/src/graphql/dto/update-todo.input.ts.hbs +8 -0
- package/dist/templates/feature/graphql/src/graphql/graphql.module.ts.hbs +33 -0
- package/dist/templates/feature/graphql/src/graphql/models/base.model.ts.hbs +13 -0
- package/dist/templates/feature/graphql/src/graphql/models/pagination.model.ts.hbs +27 -0
- package/dist/templates/feature/graphql/src/graphql/models/todo.model.ts.hbs +23 -0
- package/dist/templates/feature/graphql/src/graphql/models/user.model.ts.hbs +34 -0
- package/dist/templates/feature/graphql/src/graphql/orm/drizzle/drizzle-graphql.module.ts.hbs +42 -0
- package/dist/templates/feature/graphql/src/graphql/orm/typeorm/typeorm-graphql-relations.helper.ts.hbs +23 -0
- package/dist/templates/feature/graphql/src/graphql/resolvers/base.resolver.ts.hbs +50 -0
- package/dist/templates/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +126 -0
- package/dist/templates/feature/rest/todo-example/src/api/todo/dto/create-todo.dto.ts.hbs +13 -0
- package/dist/templates/feature/rest/todo-example/src/api/todo/dto/update-todo.dto.ts.hbs +13 -0
- package/dist/templates/feature/rest/todo-example/src/api/todo/todo.controller.ts.hbs +346 -0
- package/dist/templates/monorepo/apps/api-gateway/src/app.module.ts.hbs +15 -1
- package/dist/templates/monorepo/apps/api-gateway/src/main.ts.hbs +56 -54
- package/dist/templates/monorepo/feature/graphql/src/graphql/dto/create-todo.input.ts.hbs +13 -0
- package/dist/templates/monorepo/feature/graphql/src/graphql/dto/update-todo.input.ts.hbs +8 -0
- package/dist/templates/monorepo/feature/graphql/src/graphql/graphql.module.ts.hbs +33 -0
- package/dist/templates/monorepo/feature/graphql/src/graphql/models/base.model.ts.hbs +13 -0
- package/dist/templates/monorepo/feature/graphql/src/graphql/models/todo.model.ts.hbs +23 -0
- package/dist/templates/monorepo/feature/graphql/src/graphql/models/user.model.ts.hbs +34 -0
- package/dist/templates/monorepo/feature/graphql/src/graphql/orm/drizzle/drizzle-graphql.module.ts.hbs +16 -0
- package/dist/templates/monorepo/feature/graphql/src/graphql/orm/prisma/prisma-graphql.service.ts.hbs +9 -0
- package/dist/templates/monorepo/feature/graphql/src/graphql/orm/typeorm/typeorm-graphql-relations.helper.ts.hbs +23 -0
- package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +122 -0
- package/dist/templates/monorepo/packages/env/src/api-gateway.ts.hbs +10 -0
- package/dist/templates/monorepo/root/README.md.hbs +1 -1
- package/dist/templates/monorepo/root/pnpm-workspace.yaml.hbs +6 -0
- package/dist/templates/single-project/architecture/nest-api/.env.hbs +25 -1
- package/dist/templates/single-project/architecture/nest-api/pnpm-workspace.yaml.hbs +6 -0
- package/dist/templates/single-project/architecture/nest-api/src/main.ts.hbs +61 -55
- package/dist/templates/single-project/architecture/nest-gateway/src/main.ts.hbs +56 -54
- package/dist/templates/single-project/architecture/nest-microservice/.env.hbs +3 -0
- package/dist/templates/single-project/architecture/nest-microservice/src/main.ts.hbs +56 -54
- package/dist/templates/single-project/shared/config/env.ts.hbs +10 -0
- 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: [
|
|
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
|
|
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
|
-
|
|
201
|
-
|
|
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,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,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
|
+
}
|