@nest-arch/tui 0.1.48 → 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-LZTzRAoQ.js +1 -0
- 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/.gitignore.hbs +3 -0
- package/dist/templates/base/README.md.hbs +119 -6
- package/dist/templates/base/pnpm-workspace.yaml.hbs +9 -0
- package/dist/templates/base/src/app.controller.ts.hbs +10 -0
- package/dist/templates/base/src/app.module.ts.hbs +48 -5
- package/dist/templates/base/src/app.router.spec.ts.hbs +58 -0
- package/dist/templates/base/src/app.router.ts.hbs +26 -0
- package/dist/templates/base/src/database/adapters/interfaces/db-adapter.interface.ts.hbs +3 -3
- package/dist/templates/base/src/libs/middlewares/auth.trpc.middleware.ts.hbs +42 -0
- package/dist/templates/base/src/libs/trpc/context.ts.hbs +13 -0
- package/dist/templates/base/src/libs/trpc/error.handler.ts.hbs +10 -0
- package/dist/templates/base/src/libs/trpc/inner.context.ts.hbs +7 -0
- package/dist/templates/base/src/libs/trpc/protected.ts.hbs +12 -0
- package/dist/templates/base/src/libs/trpc/scripts/fix-trpc-generated.mjs.hbs +21 -0
- package/dist/templates/base/src/libs/trpc/scripts/watch-trpc.mjs.hbs +91 -0
- package/dist/templates/base/src/main.ts.hbs +78 -1
- package/dist/templates/extras/swagger/create-docs.ts.hbs +276 -0
- 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.controller.ts.hbs +10 -0
- package/dist/templates/monorepo/apps/api-gateway/src/app.module.ts.hbs +41 -2
- package/dist/templates/monorepo/apps/api-gateway/src/main.ts.hbs +75 -0
- 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 +79 -0
- package/dist/templates/single-project/architecture/nest-gateway/src/main.ts.hbs +75 -0
- package/dist/templates/single-project/architecture/nest-microservice/.env.hbs +3 -0
- package/dist/templates/single-project/architecture/nest-microservice/src/main.ts.hbs +76 -0
- package/dist/templates/single-project/shared/config/env.ts.hbs +10 -0
- package/package.json +3 -3
- package/dist/constants-D_Ow0fiZ.js +0 -1
|
@@ -2,16 +2,93 @@ 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, StandardSchemaValidationPipe } from "@nestjs/common";
|
|
5
6
|
|
|
6
7
|
import { AppModule } from "./app.module.js";
|
|
7
8
|
import { env } from "./config/env.js";
|
|
9
|
+
{{#if swagger}}
|
|
10
|
+
import { createDocs } from "./docs/create-docs.js";
|
|
11
|
+
{{/if}}
|
|
12
|
+
{{#if (and swagger useSwaggerScalar)}}
|
|
13
|
+
import { apiReference } from "@scalar/nestjs-api-reference";
|
|
14
|
+
{{/if}}
|
|
15
|
+
{{#if (and swagger (eq useSwaggerScalar false))}}
|
|
16
|
+
import { SwaggerModule } from "@nestjs/swagger";
|
|
17
|
+
{{/if}}
|
|
18
|
+
{{#if (and swagger (or (eq auth "better-auth") (eq auth "better-auth:stateless") hasBetterAuthWithoutRest))}}
|
|
19
|
+
import { auth } from "./auth/auth.js";
|
|
20
|
+
{{/if}}
|
|
8
21
|
|
|
9
22
|
async function bootstrap() {
|
|
10
|
-
const app = await NestFactory.create{{#if (eq httpProvider "fastify")}}<NestFastifyApplication>(AppModule, new FastifyAdapter(){{else}}(AppModule{{/if}}{{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}, { bodyParser: false }{{/if}});
|
|
23
|
+
const app = await NestFactory.create{{#if (eq httpProvider "fastify")}}<NestFastifyApplication>(AppModule, new FastifyAdapter(){{else}}(AppModule{{/if}}{{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless") hasBetterAuthWithoutRest)}}, { bodyParser: false }{{/if}});
|
|
11
24
|
app.enableCors({
|
|
12
25
|
origin: env.CORS_ORIGIN,
|
|
13
26
|
});
|
|
27
|
+
{{#if hasRest}}
|
|
14
28
|
app.setGlobalPrefix("api");
|
|
29
|
+
{{/if}}
|
|
30
|
+
app.useGlobalPipes(new StandardSchemaValidationPipe());
|
|
31
|
+
app.enableShutdownHooks();
|
|
32
|
+
{{#if swagger}}
|
|
33
|
+
const docsPath = "/docs";
|
|
34
|
+
{{#if useSwaggerScalar}}
|
|
35
|
+
app.use(
|
|
36
|
+
docsPath,
|
|
37
|
+
apiReference({
|
|
38
|
+
content: await createDocs(app, {
|
|
39
|
+
description: "{{description}}",
|
|
40
|
+
title: "{{projectNamePascalCase}} API",
|
|
41
|
+
integrations: {
|
|
42
|
+
{{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless") hasBetterAuthWithoutRest)}}
|
|
43
|
+
betterAuth: {
|
|
44
|
+
auth,
|
|
45
|
+
pathPrefix: "/api/auth",
|
|
46
|
+
},
|
|
47
|
+
{{/if}}
|
|
48
|
+
{{#if (includes apis "trpc")}}
|
|
49
|
+
trpc: {
|
|
50
|
+
routerFilePath: "./src/libs/trpc/@generated/server.ts",
|
|
51
|
+
pathPrefix: "/trpc",
|
|
52
|
+
},
|
|
53
|
+
{{/if}}
|
|
54
|
+
zod: {
|
|
55
|
+
withFastify: {{#if (eq httpProvider "fastify")}}true{{else}}false{{/if}},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
version: "1.0.0",
|
|
59
|
+
}),
|
|
60
|
+
pageTitle: "{{projectNamePascalCase}} API Docs",
|
|
61
|
+
theme: "default",
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
{{else}}
|
|
65
|
+
const document = await createDocs(app, {
|
|
66
|
+
title: "{{projectNamePascalCase}} API",
|
|
67
|
+
description: "{{description}}",
|
|
68
|
+
version: "1.0.0",
|
|
69
|
+
integrations: {
|
|
70
|
+
{{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless") hasBetterAuthWithoutRest)}}
|
|
71
|
+
betterAuth: {
|
|
72
|
+
auth,
|
|
73
|
+
pathPrefix: "/api/auth",
|
|
74
|
+
},
|
|
75
|
+
{{/if}}
|
|
76
|
+
{{#if (includes apis "trpc")}}
|
|
77
|
+
trpc: {
|
|
78
|
+
routerFilePath: "./src/trpc/router.ts",
|
|
79
|
+
pathPrefix: "/trpc",
|
|
80
|
+
},
|
|
81
|
+
{{/if}}
|
|
82
|
+
zod: {
|
|
83
|
+
withFastify: {{#if (eq httpProvider "fastify")}}true{{else}}false{{/if}},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
SwaggerModule.setup(docsPath, app, document);
|
|
88
|
+
{{/if}}
|
|
89
|
+
Logger.log(`Swagger documentation available at http://localhost:${env.PORT ?? 3000}${docsPath}`, "SwaggerDocs");
|
|
90
|
+
{{/if}}
|
|
15
91
|
await app.listen(env.PORT ?? 3000);
|
|
92
|
+
Logger.log(`Started backend server on http://localhost:${env.PORT ?? 3000}/api`, "Main");
|
|
16
93
|
}
|
|
17
94
|
await bootstrap();
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, import/no-unresolved */
|
|
3
|
+
/* oxlint-disable import/no-unresolved */
|
|
4
|
+
// oxlint-disable no-unused-vars
|
|
5
|
+
|
|
6
|
+
import type { INestApplication } from "@nestjs/common";
|
|
7
|
+
{{#if hasRest}}
|
|
8
|
+
import {
|
|
9
|
+
DocumentBuilder,
|
|
10
|
+
SwaggerModule,
|
|
11
|
+
type OpenAPIObject,
|
|
12
|
+
} from "@nestjs/swagger";
|
|
13
|
+
{{else}}
|
|
14
|
+
import type { OpenAPIObject } from "@nestjs/swagger";
|
|
15
|
+
{{/if}}
|
|
16
|
+
|
|
17
|
+
interface DocsIntegrations {
|
|
18
|
+
betterAuth?: {
|
|
19
|
+
auth?: unknown;
|
|
20
|
+
pathPrefix?: string;
|
|
21
|
+
prefix?: string;
|
|
22
|
+
};
|
|
23
|
+
trpc?: {
|
|
24
|
+
exportName?: string;
|
|
25
|
+
pathPrefix?: string;
|
|
26
|
+
routerFilePath?: string;
|
|
27
|
+
title?: string;
|
|
28
|
+
version?: string;
|
|
29
|
+
};
|
|
30
|
+
zod?: {
|
|
31
|
+
withFastify?: boolean;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CreateDocsOptions {
|
|
36
|
+
title?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
version?: string;
|
|
39
|
+
extraSpecs?: OpenAPIObject[];
|
|
40
|
+
integrations?: DocsIntegrations;
|
|
41
|
+
}
|
|
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
|
+
|
|
95
|
+
function addPrefixToPaths(paths: Record<string, unknown>, prefix: string) {
|
|
96
|
+
return Object.fromEntries(
|
|
97
|
+
Object.entries(paths).map(([path, value]) => [`${prefix}${path}`, value])
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function mergeOpenApiSpecs(baseSpec: OpenAPIObject, ...extraSpecs: OpenAPIObject[]) {
|
|
102
|
+
const specs = [baseSpec, ...extraSpecs];
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
...baseSpec,
|
|
106
|
+
components: {
|
|
107
|
+
...baseSpec.components,
|
|
108
|
+
parameters: Object.assign(
|
|
109
|
+
{},
|
|
110
|
+
...specs.map((spec) => spec.components?.parameters ?? {})
|
|
111
|
+
),
|
|
112
|
+
responses: Object.assign(
|
|
113
|
+
{},
|
|
114
|
+
...specs.map((spec) => spec.components?.responses ?? {})
|
|
115
|
+
),
|
|
116
|
+
schemas: Object.assign(
|
|
117
|
+
{},
|
|
118
|
+
...specs.map((spec) => spec.components?.schemas ?? {})
|
|
119
|
+
),
|
|
120
|
+
securitySchemes: Object.assign(
|
|
121
|
+
{},
|
|
122
|
+
...specs.map((spec) => spec.components?.securitySchemes ?? {})
|
|
123
|
+
),
|
|
124
|
+
},
|
|
125
|
+
info: baseSpec.info,
|
|
126
|
+
openapi: baseSpec.openapi ?? "3.1.0",
|
|
127
|
+
paths: Object.assign({}, ...specs.map((spec) => spec.paths ?? {})),
|
|
128
|
+
security: specs.find((spec) => spec.security)?.security,
|
|
129
|
+
servers: baseSpec.servers,
|
|
130
|
+
tags: specs.flatMap((spec) => (spec.tags ?? []) as unknown[]),
|
|
131
|
+
} as OpenAPIObject;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function applyBetterAuthSpec(
|
|
135
|
+
options?: NonNullable<DocsIntegrations["betterAuth"]>
|
|
136
|
+
) {
|
|
137
|
+
if (!options?.auth) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const authInstance = options.auth as {
|
|
142
|
+
api?: {
|
|
143
|
+
generateOpenAPISchema?: () => OpenAPIObject | Promise<OpenAPIObject>;
|
|
144
|
+
};
|
|
145
|
+
generateOpenAPISchema?: () => OpenAPIObject | Promise<OpenAPIObject>;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const schemaGenerator = authInstance.api?.generateOpenAPISchema ?? authInstance.generateOpenAPISchema;
|
|
149
|
+
|
|
150
|
+
if (typeof schemaGenerator !== "function") {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const generatedSchema = await schemaGenerator();
|
|
156
|
+
const authSpec = generatedSchema as OpenAPIObject;
|
|
157
|
+
const prefix = options.pathPrefix ?? options.prefix ?? "/auth";
|
|
158
|
+
|
|
159
|
+
if (authSpec.paths) {
|
|
160
|
+
authSpec.paths = addPrefixToPaths(
|
|
161
|
+
authSpec.paths as Record<string, unknown>,
|
|
162
|
+
prefix
|
|
163
|
+
) as unknown as OpenAPIObject["paths"];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return authSpec;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function applyTrpcSpec(
|
|
173
|
+
options?: NonNullable<DocsIntegrations["trpc"]>
|
|
174
|
+
) {
|
|
175
|
+
if (!options?.routerFilePath) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
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;
|
|
185
|
+
|
|
186
|
+
if (typeof generateOpenAPIDocument !== "function") {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const generatedDocument = await generateOpenAPIDocument(options.routerFilePath, {
|
|
191
|
+
exportName: options.exportName ?? "AppRouter",
|
|
192
|
+
title: options.title ?? "tRPC API",
|
|
193
|
+
version: options.version ?? "1.0.0",
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const trpcSpec = generatedDocument as OpenAPIObject;
|
|
197
|
+
const prefix = options.pathPrefix ?? "/trpc";
|
|
198
|
+
|
|
199
|
+
if (trpcSpec.paths) {
|
|
200
|
+
trpcSpec.paths = addPrefixToPaths(
|
|
201
|
+
trpcSpec.paths as Record<string, unknown>,
|
|
202
|
+
prefix
|
|
203
|
+
) as unknown as OpenAPIObject["paths"];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return trpcSpec;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function createDocs(
|
|
213
|
+
app: INestApplication,
|
|
214
|
+
options: CreateDocsOptions = {}
|
|
215
|
+
): Promise<OpenAPIObject> {
|
|
216
|
+
{{#if hasRest}}
|
|
217
|
+
const config = new DocumentBuilder()
|
|
218
|
+
.setTitle(options.title ?? "NestJS API")
|
|
219
|
+
.setDescription(options.description ?? "Generated with Nest Arch")
|
|
220
|
+
.setVersion(options.version ?? "1.0.0")
|
|
221
|
+
.build();
|
|
222
|
+
|
|
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;
|
|
246
|
+
|
|
247
|
+
if (!baseSpec.paths || Object.keys(baseSpec.paths).length === 0) {
|
|
248
|
+
baseSpec = {
|
|
249
|
+
...baseSpec,
|
|
250
|
+
info: baseSpec.info ?? {
|
|
251
|
+
title: options.title ?? "NestJS API",
|
|
252
|
+
version: options.version ?? "1.0.0",
|
|
253
|
+
},
|
|
254
|
+
openapi: baseSpec.openapi ?? "3.1.0",
|
|
255
|
+
paths: {},
|
|
256
|
+
tags: [],
|
|
257
|
+
} as OpenAPIObject;
|
|
258
|
+
}
|
|
259
|
+
{{else}}
|
|
260
|
+
const baseSpec = createEmptyBaseSpec(options);
|
|
261
|
+
{{/if}}
|
|
262
|
+
|
|
263
|
+
const additionalSpecs: OpenAPIObject[] = [];
|
|
264
|
+
|
|
265
|
+
const betterAuthSpec = await applyBetterAuthSpec(options.integrations?.betterAuth);
|
|
266
|
+
if (betterAuthSpec) {
|
|
267
|
+
additionalSpecs.push(betterAuthSpec);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const trpcSpec = await applyTrpcSpec(options.integrations?.trpc);
|
|
271
|
+
if (trpcSpec) {
|
|
272
|
+
additionalSpecs.push(trpcSpec);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return mergeOpenApiSpecs(baseSpec, ...additionalSpecs);
|
|
276
|
+
}
|
|
@@ -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
|
+
}
|