@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.
- package/README.md +7 -0
- package/dist/cli.js +1 -1
- package/dist/{constants-BbJ71cex.js → constants-DBtqeoTh.js} +1 -1
- package/dist/index.js +97 -79
- package/dist/templates/base/.env.example.hbs +36 -0
- package/dist/templates/base/.env.hbs +8 -1
- package/dist/templates/base/README.md.hbs +1 -1
- package/dist/templates/base/pnpm-workspace.yaml.hbs +9 -0
- package/dist/templates/base/src/app.module.ts.hbs +21 -1
- package/dist/templates/base/src/database/db.module.ts.hbs +2 -4
- package/dist/templates/base/src/main.ts.hbs +11 -1
- package/dist/templates/extras/swagger/create-docs.ts.hbs +81 -25
- 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 +171 -0
- package/dist/templates/feature/rest/todo-example/src/api/todo/todo.module.ts.hbs +24 -0
- package/dist/templates/feature/rest/todo-example/src/api/todo/todo.router.ts.hbs +76 -0
- package/dist/templates/feature/rest/todo-example/src/api/todo/todo.service.ts.hbs +145 -0
- package/dist/templates/monorepo/apps/api-gateway/src/app.module.ts.hbs +21 -1
- package/dist/templates/monorepo/apps/api-gateway/src/main.ts.hbs +62 -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 +9 -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 +9 -0
- package/dist/templates/single-project/architecture/nest-api/src/main.ts.hbs +67 -55
- package/dist/templates/single-project/architecture/nest-gateway/src/main.ts.hbs +62 -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 +62 -54
- package/dist/templates/single-project/shared/config/env.ts.hbs +10 -0
- 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=
|
|
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: [
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
201
|
-
|
|
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,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
|
+
}
|