@nest-arch/tui 0.2.8 → 0.4.3

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 (92) hide show
  1. package/README.md +20 -42
  2. package/dist/cli.js +1 -1
  3. package/dist/constants-B5MuV-iH.js +1 -0
  4. package/dist/index.js +365 -98
  5. package/dist/templates/addons/husky/pre-commit.hbs +11 -26
  6. package/dist/templates/base/.env.example.hbs +13 -2
  7. package/dist/templates/base/.env.hbs +11 -2
  8. package/dist/templates/base/.gitignore.hbs +2 -1
  9. package/dist/templates/base/.yarnrc.yml.hbs +0 -5
  10. package/dist/templates/base/README.md.hbs +49 -5
  11. package/dist/templates/base/nest-cli.json.hbs +2 -1
  12. package/dist/templates/base/pnpm-workspace.yaml.hbs +15 -0
  13. package/dist/templates/base/prisma.config.ts.hbs +32 -6
  14. package/dist/templates/base/src/app.controller.spec.ts.hbs +3 -11
  15. package/dist/templates/base/src/app.controller.ts.hbs +2 -2
  16. package/dist/templates/base/src/app.module.ts.hbs +76 -5
  17. package/dist/templates/base/src/app.router.ts.hbs +3 -4
  18. package/dist/templates/base/src/database/adapters/interfaces/db-adapter.interface.ts.hbs +7 -0
  19. package/dist/templates/base/src/database/adapters/mongo/prisma-mongo.adapter.ts.hbs +36 -4
  20. package/dist/templates/base/src/database/adapters/mongo/raw-mongo.adapter.ts.hbs +17 -12
  21. package/dist/templates/base/src/database/adapters/sql/drizzle-sql.adapter.ts.hbs +9 -0
  22. package/dist/templates/base/src/database/adapters/sql/prisma-sql.adapter.ts.hbs +8 -5
  23. package/dist/templates/base/src/database/adapters/sql/raw-sql.adapter.ts.hbs +37 -0
  24. package/dist/templates/base/src/database/adapters/sql/typeorm-sql.adapter.ts.hbs +8 -3
  25. package/dist/templates/base/src/database/db.module.ts.hbs +55 -42
  26. package/dist/templates/base/src/database/db.service.ts.hbs +60 -6
  27. package/dist/templates/base/src/database/entities/app.entity.ts.hbs +25 -0
  28. package/dist/templates/base/src/database/entities/todo.entity.ts.hbs +12 -7
  29. package/dist/templates/base/src/database/index.ts.hbs +20 -0
  30. package/dist/templates/base/src/database/orm/drizzle/schema.ts.hbs +80 -7
  31. package/dist/templates/base/src/database/orm/typeorm/data-source.ts.hbs +23 -4
  32. package/dist/templates/base/src/libs/middlewares/auth.trpc.middleware.ts.hbs +47 -0
  33. package/dist/templates/base/src/libs/middlewares/rate-limit.trpc.middleware.ts.hbs +43 -0
  34. package/dist/templates/base/src/libs/trpc/error.handler.ts.hbs +1 -1
  35. package/dist/templates/base/src/libs/trpc/inner.context.ts.hbs +6 -3
  36. package/dist/templates/base/src/main.ts.hbs +34 -10
  37. package/dist/templates/base/src/modules/users/entities/user.entity.ts.hbs +22 -0
  38. package/dist/templates/base/test/app.e2e-spec.ts.hbs +841 -6
  39. package/dist/templates/base/test/bootstrap.e2e-spec.ts.hbs +84 -0
  40. package/dist/templates/base/tsconfig.build.json.hbs +2 -0
  41. package/dist/templates/base/vitest.config.e2e.ts.hbs +4 -3
  42. package/dist/templates/base/vitest.config.ts.hbs +4 -3
  43. package/dist/templates/base/yarn.lock.hbs +1 -0
  44. package/dist/templates/extras/docker/.dockerignore.hbs +8 -1
  45. package/dist/templates/extras/docker/Dockerfile.hbs +11 -10
  46. package/dist/templates/extras/health/src/health/health.controller.ts.hbs +54 -0
  47. package/dist/templates/extras/health/src/health/health.module.ts.hbs +9 -0
  48. package/dist/templates/extras/swagger/create-docs.ts.hbs +13 -22
  49. package/dist/templates/feature/graphql/src/graphql/graphql.module.ts.hbs +6 -10
  50. package/dist/templates/feature/graphql/src/graphql/models/todo.model.ts.hbs +4 -0
  51. package/dist/templates/feature/graphql/src/graphql/orm/drizzle/drizzle-graphql.module.ts.hbs +6 -4
  52. package/dist/templates/feature/graphql/src/graphql/resolvers/health.resolver.ts.hbs +14 -0
  53. package/dist/templates/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +210 -47
  54. package/dist/templates/feature/rest/todo-example/src/api/todo/dto/create-todo.dto.ts.hbs +12 -0
  55. package/dist/templates/feature/rest/todo-example/src/api/todo/dto/update-todo.dto.ts.hbs +12 -0
  56. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.controller.ts.hbs +21 -9
  57. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.module.ts.hbs +1 -3
  58. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.router.ts.hbs +41 -13
  59. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.service.ts.hbs +157 -33
  60. package/dist/templates/monorepo/apps/api-gateway/package.json.hbs +2 -3
  61. package/dist/templates/monorepo/apps/api-gateway/src/app.controller.spec.ts.hbs +6 -1
  62. package/dist/templates/monorepo/apps/api-gateway/src/app.controller.ts.hbs +6 -0
  63. package/dist/templates/monorepo/apps/api-gateway/src/app.module.ts.hbs +32 -1
  64. package/dist/templates/monorepo/apps/api-gateway/src/app.service.ts.hbs +1 -1
  65. package/dist/templates/monorepo/apps/api-gateway/src/main.ts.hbs +13 -4
  66. package/dist/templates/monorepo/apps/api-gateway/test/app.e2e-spec.ts.hbs +45 -1
  67. package/dist/templates/monorepo/apps/api-gateway/tsconfig.build.json.hbs +2 -1
  68. package/dist/templates/monorepo/apps/api-gateway/vitest.config.e2e.ts.hbs +3 -2
  69. package/dist/templates/monorepo/apps/api-gateway/vitest.config.ts.hbs +3 -2
  70. package/dist/templates/monorepo/feature/graphql/src/graphql/graphql.module.ts.hbs +6 -10
  71. package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/health.resolver.ts.hbs +14 -0
  72. package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +151 -42
  73. package/dist/templates/monorepo/packages/env/src/api-gateway.ts.hbs +16 -3
  74. package/dist/templates/monorepo/packages/shared/package.json.hbs +1 -0
  75. package/dist/templates/monorepo/packages/shared/src/auth.ts.hbs +6 -0
  76. package/dist/templates/monorepo/root/.gitignore.hbs +1 -0
  77. package/dist/templates/monorepo/root/pnpm-workspace.yaml.hbs +19 -2
  78. package/dist/templates/monorepo/root/tsconfig.json.hbs +1 -1
  79. package/dist/templates/monorepo/root/turbo.json.hbs +2 -0
  80. package/dist/templates/single-project/architecture/nest-api/.env.hbs +9 -1
  81. package/dist/templates/single-project/architecture/nest-api/package.json.hbs +1 -2
  82. package/dist/templates/single-project/architecture/nest-api/src/main.ts.hbs +33 -9
  83. package/dist/templates/single-project/architecture/nest-gateway/src/main.ts.hbs +3 -3
  84. package/dist/templates/single-project/architecture/nest-microservice/.env.hbs +18 -0
  85. package/dist/templates/single-project/architecture/nest-microservice/proto/app.proto.hbs +26 -0
  86. package/dist/templates/single-project/architecture/nest-microservice/src/app.controller.ts.hbs +23 -4
  87. package/dist/templates/single-project/architecture/nest-microservice/src/app.service.ts.hbs +6 -2
  88. package/dist/templates/single-project/architecture/nest-microservice/src/main.ts.hbs +16 -5
  89. package/dist/templates/single-project/architecture/nest-microservice/test/microservice.e2e-spec.ts.hbs +70 -0
  90. package/dist/templates/single-project/shared/config/env.ts.hbs +38 -3
  91. package/package.json +5 -2
  92. package/dist/constants-Cgtoxhmd.js +0 -1
@@ -1,31 +1,16 @@
1
- {{#if isPnpm}}pnpm test{{else if isYarn}}yarn test{{else if isBun}}bun run test{{else}}npm test{{/if}}
2
-
3
- # ultracite
4
1
  #!/bin/sh
5
- # Exit on any error
6
2
  set -e
7
3
 
8
- # Check if there are any staged files
9
- STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)
10
- if [ -z "$STAGED_FILES" ]; then
11
- echo "No staged files to format"
12
- exit 0
13
- fi
14
-
15
- # Run formatter
16
- {{#if isPnpm}}pnpm dlx ultracite fix{{else if isYarn}}yarn dlx ultracite fix{{else if isBun}}bunx ultracite fix{{else}}npx --yes ultracite fix{{/if}}
17
- FORMAT_EXIT_CODE=$?
18
-
19
- # Re-stage files that were already staged
20
- echo "$STAGED_FILES" | while IFS= read -r file; do
21
- if [ -f "$file" ]; then
22
- git add -- "$file"
23
- fi
24
- done
4
+ {{#if isPnpm}}pnpm test{{else if isYarn}}yarn test{{else if isBun}}bun run test{{else}}npm test{{/if}}
25
5
 
26
- if [ $FORMAT_EXIT_CODE -ne 0 ]; then
27
- echo "Ultracite found issues that could not be auto-fixed."
28
- exit $FORMAT_EXIT_CODE
6
+ {{#if hasFormatter}}
7
+ STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)
8
+ if [ -n "$STAGED_FILES" ]; then
9
+ {{#if isPnpm}}pnpm run fix{{else if isYarn}}yarn run fix{{else if isBun}}bun run fix{{else}}npm run fix{{/if}}
10
+ echo "$STAGED_FILES" | while IFS= read -r file; do
11
+ if [ -f "$file" ]; then
12
+ git add -- "$file"
13
+ fi
14
+ done
29
15
  fi
30
-
31
- echo "✨ Files formatted by Ultracite"
16
+ {{/if}}
@@ -2,6 +2,8 @@
2
2
  PORT=3000
3
3
  NODE_ENV=development
4
4
  CORS_ORIGIN=http://localhost:3001
5
+ # Comma-separated trusted reverse proxy IP addresses or CIDRs
6
+ TRUSTED_PROXY_IPS={{#each trustedProxyIps}}{{#unless @first}},{{/unless}}{{this}}{{/each}}
5
7
 
6
8
  {{#if hasDatabase}}
7
9
  # Database
@@ -13,7 +15,7 @@ POSTGRES_URL=postgresql://postgres:postgres@localhost:5432/{{projectNameKebab}}_
13
15
  MYSQL_URL=mysql://root:mysql@localhost:3306/{{projectNameKebab}}_db
14
16
  {{/if}}
15
17
  {{#if hasSqlite}}
16
- SQLITE_PATH=file:./src/database/dev.db
18
+ SQLITE_PATH=./src/database/dev.db
17
19
  {{/if}}
18
20
  {{#if hasMongodb}}
19
21
  MONGODB_URL=mongodb://localhost:27017/{{projectNameKebab}}_db
@@ -24,13 +26,22 @@ MONGODB_URL=mongodb://localhost:27017/{{projectNameKebab}}_db
24
26
  GRAPHQL_PLAYGROUND=true
25
27
 
26
28
  {{/if}}
27
- {{#if (and (eq auth "passport") hasDatabase)}}
29
+ {{#if (eq auth "passport")}}
28
30
  # JWT
29
31
  JWT_ACCESS_SECRET=change-me-to-a-random-secret-at-least-32-characters-long
32
+ {{#if hasDatabase}}
30
33
  JWT_REFRESH_SECRET=change-me-to-a-different-random-secret-at-least-32-characters
31
34
  {{/if}}
35
+ {{/if}}
32
36
  {{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
33
37
  # Better Auth
34
38
  BETTER_AUTH_SECRET=change-me-to-a-random-secret-at-least-32-characters-long
35
39
  BETTER_AUTH_URL=http://localhost:3000
40
+ {{#if (eq auth "better-auth:stateless")}}
41
+ # OIDC provider; register http://localhost:3000/api/auth/callback/$OIDC_PROVIDER_ID as its callback URL
42
+ OIDC_PROVIDER_ID=company-oidc
43
+ OIDC_CLIENT_ID=replace-with-provider-client-id
44
+ OIDC_CLIENT_SECRET=replace-with-provider-client-secret
45
+ OIDC_DISCOVERY_URL=https://identity.example.com/.well-known/openid-configuration
46
+ {{/if}}
36
47
  {{/if}}
@@ -1,5 +1,6 @@
1
1
  CORS_ORIGIN=http://localhost:3001
2
2
  PORT=3000
3
+ TRUSTED_PROXY_IPS={{#each trustedProxyIps}}{{#unless @first}},{{/unless}}{{this}}{{/each}}
3
4
  {{#if hasGraphql}}
4
5
  GRAPHQL_PLAYGROUND=true
5
6
  {{/if}}
@@ -11,16 +12,24 @@ POSTGRES_URL=postgresql://postgres:postgres@localhost:5432/{{projectNameKebab}}_
11
12
  MYSQL_URL=mysql://root:mysql@localhost:3306/{{projectNameKebab}}_db
12
13
  {{/if}}
13
14
  {{#if hasSqlite}}
14
- SQLITE_PATH=file:./src/database/dev.db
15
+ SQLITE_PATH=./src/database/dev.db
15
16
  {{/if}}
16
17
  {{#if hasMongodb}}
17
18
  MONGODB_URL=mongodb://localhost:27017/{{projectNameKebab}}_db
18
19
  {{/if}}
19
- {{#if (and (eq auth "passport") hasDatabase)}}
20
+ {{#if (eq auth "passport")}}
20
21
  JWT_ACCESS_SECRET=change-me-to-a-random-secret-at-least-32-characters-long
22
+ {{#if hasDatabase}}
21
23
  JWT_REFRESH_SECRET=change-me-to-a-different-random-secret-at-least-32-characters
22
24
  {{/if}}
25
+ {{/if}}
23
26
  {{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
24
27
  BETTER_AUTH_SECRET=change-me-to-a-random-secret-at-least-32-characters-long
25
28
  BETTER_AUTH_URL=http://localhost:3000
29
+ {{#if (eq auth "better-auth:stateless")}}
30
+ OIDC_PROVIDER_ID=company-oidc
31
+ OIDC_CLIENT_ID=replace-with-provider-client-id
32
+ OIDC_CLIENT_SECRET=replace-with-provider-client-secret
33
+ OIDC_DISCOVERY_URL=https://identity.example.com/.well-known/openid-configuration
34
+ {{/if}}
26
35
  {{/if}}
@@ -43,6 +43,7 @@ lerna-debug.log*
43
43
 
44
44
  # dotenv environment variable files
45
45
  .env
46
+ .env.test
46
47
  .env.development.local
47
48
  .env.test.local
48
49
  .env.production.local
@@ -59,4 +60,4 @@ pids
59
60
  *.pid.lock
60
61
 
61
62
  # Diagnostic reports (https://nodejs.org/api/report.html)
62
- report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
63
+ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
@@ -1,6 +1 @@
1
1
  nodeLinker: node-modules
2
- catalog:
3
- "@types/node": ^22.13.14
4
- dotenv: ^17.2.2
5
- typescript: ^6.0.2
6
- zod: ^4.4.3
@@ -36,6 +36,12 @@ npm run start{{/if}}
36
36
  {{#if isPnpm}}pnpm test:cov{{else if isYarn}}yarn test:cov{{else if isBun}}bun run test:cov{{else}}npm run test:cov{{/if}}
37
37
  ```
38
38
 
39
+ {{#if hasRateLimiting}}
40
+ ## Reverse Proxy
41
+
42
+ Set `TRUSTED_PROXY_IPS` to the IP addresses or CIDRs of your trusted reverse proxies. Leave it empty when clients connect directly. The application uses this allowlist when reading forwarded client IP headers for rate limiting.
43
+ {{/if}}
44
+
39
45
  {{#if usePrisma}}
40
46
  ## Database Setup
41
47
 
@@ -79,12 +85,24 @@ This project uses **TypeORM** as the ORM. Available commands:
79
85
  {{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
80
86
  ## Auth Setup
81
87
 
82
- This project uses **Better Auth**. Generate the database schema by running:
88
+ {{#if (eq auth "better-auth:stateless")}}
89
+ This project uses **Better Auth** with encrypted stateless cookie sessions and an external OpenID Connect provider. Set `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `OIDC_PROVIDER_ID`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, and `OIDC_DISCOVERY_URL` in `.env`. Register `${BETTER_AUTH_URL}/api/auth/callback/${OIDC_PROVIDER_ID}` with the provider, then begin the authorization-code flow through Better Auth's social sign-in endpoint. The generated configuration uses discovery, PKCE, and issuer validation.
83
90
 
84
- ```bash
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
- ```
87
- See https://better-auth.com/docs/adapters/prisma for more details.
91
+ No local registration, password login, database migration, or immediate server-side revocation is included in this profile.
92
+ {{else}}
93
+ This project uses **Better Auth** with database-backed sessions. Set `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` in `.env` before running the generated scripts.
94
+
95
+ Run `auth:generate` with your package manager. The script uses the installed CLI version.
96
+ {{#if usePrisma}}
97
+ Then run `prisma:generate`, `prisma:migrate --name auth`, and `build`. For deployments, apply the committed migrations with `prisma:migrate:deploy`. Auth models live in `src/database/orm/prisma/auth.prisma`; keep application models in `schema.prisma` or other files in that directory. `auth:generate` replaces only the auth-owned file (using `auth-schema.prisma` as CLI output). Inspect that diff after changing plugins.
98
+ {{else if useDrizzle}}
99
+ Then run `db:generate`, `db:migrate`, and `build`. Auth owns `src/database/orm/drizzle/auth-schema.ts`; application tables remain in `schema.ts`. Commit generated migration files. SQLite requires no Docker service.
100
+ {{else}}
101
+ Then run `auth:migrate` and `build`.
102
+ {{/if}}
103
+
104
+ The runtime auth module receives the database client owned by Nest. CLI schema discovery uses separate configuration. `GET /api/auth-profile` is a protected example for the Prisma and Drizzle profiles.
105
+ {{/if}}
88
106
 
89
107
  ### Usage
90
108
 
@@ -185,3 +203,29 @@ import { AppRouterClass as AppRouter } from "./app.router.js";
185
203
  export class AppModule {}
186
204
  ```
187
205
  {{/if}}
206
+
207
+ {{#if isPassportAuth}}
208
+ ## Passport authentication
209
+
210
+ {{#if hasDatabase}}
211
+ Set a random `JWT_ACCESS_SECRET` of at least 32 characters. Configure `JWT_ISSUER`, `JWT_AUDIENCE`, and `JWT_KEY_ID` consistently across instances. Apply Prisma migrations before starting.
212
+
213
+ Register with `POST /api/auth/register` and sign in with `POST /api/auth/login`, using `{ "email": "user@example.com", "password": "a-long-unique-password" }`. Send the returned access token in `Authorization: Bearer <token>` to `GET /api/auth/profile`. Access tokens expire after 15 minutes. Passwords are hashed with bcrypt; token payloads contain no password or role supplied by the client.
214
+
215
+ Refresh with `POST /api/auth/refresh` and `{ "refreshToken": "..." }`. Refresh tokens rotate on every use and expire seven days after login. Reusing a consumed token revokes the entire login session, including access tokens. Concurrent refresh requests therefore require client-side serialization. `POST /api/auth/logout` takes the same body and revokes that login session. Tokens are returned in JSON; no cookie transport is generated.
216
+
217
+ Only hashes of refresh tokens are stored. Schedule deletion of expired refresh-token families according to your retention policy. Secret replacement invalidates existing access tokens; overlapping signing keys are not implemented in this baseline.
218
+ {{else}}
219
+ Configure `OIDC_JWKS_URI`, `OIDC_ISSUER`, and `OIDC_AUDIENCE` for your trusted identity provider. Send its RS256 access token in `Authorization: Bearer <token>` to `GET /api/auth/profile`. The API verifies the signature using the provider's cached, rate-limited JWKS keys and checks the issuer, audience, and expiration. This API does not accept passwords or issue local tokens.
220
+ {{/if}}
221
+ {{/if}}
222
+ {{#if hasGraphql}}
223
+ ## GraphQL
224
+
225
+ Send queries to `POST /graphql`. `{ hello }` is always available. With the Todo example, `todos`, `createTodo`, `updateTodo`, and `deleteTodo` operate on PostgreSQL. The first profile is unauthenticated; it does not include subscriptions or federation.
226
+ {{/if}}
227
+ {{#if hasMongodb}}
228
+ ## MongoDB
229
+
230
+ Set `MONGODB_URL` including the database name. The native client connects and pings MongoDB during startup and closes with the Nest application. No Prisma migration commands are required. Todo identifiers are serialized ObjectId strings; malformed IDs return 400 and missing mutation targets return 404. Listing returns at most 100 items in identifier order. This baseline performs single-document writes and does not promise multi-document transactions.
231
+ {{/if}}
@@ -3,6 +3,7 @@
3
3
  "collection": "@nestjs/schematics",
4
4
  "sourceRoot": "src",
5
5
  "compilerOptions": {
6
- "deleteOutDir": true
6
+ "deleteOutDir": true{{#if (includes microservices "grpc")}},
7
+ "assets": ["proto/**/*"]{{/if}}
7
8
  }
8
9
  }
@@ -1,5 +1,12 @@
1
1
  allowBuilds:
2
+ {{#if useDrizzle}}
3
+ esbuild: true
4
+ {{/if}}
5
+ protobufjs: true
2
6
  "@nestjs/core": true
7
+ {{#if useTypescript7}}
8
+ esbuild: true
9
+ {{/if}}
3
10
  {{#if swagger}}
4
11
  "@scarf/scarf": true
5
12
  {{/if}}
@@ -9,6 +16,14 @@ allowBuilds:
9
16
  {{#if usePrisma}}
10
17
  prisma: true
11
18
  "@prisma/engines": true
19
+ {{#if isPrisma6}}
20
+ "@prisma/client": true
21
+ {{/if}}
22
+ {{#if isPrisma8}}
23
+ esbuild: true
24
+ msgpackr-extract: true
25
+ workerd: true
26
+ {{/if}}
12
27
  {{/if}}
13
28
  {{#if (and (eq auth "passport") hasDatabase)}}
14
29
  bcrypt: true
@@ -1,11 +1,37 @@
1
- import "dotenv/config";
2
- import { defineConfig, env } from "prisma/config";
1
+ {{#if isMonorepo}}
2
+ import { env } from "@repo/env/api-gateway";
3
+ {{else}}
4
+ import { env } from "./src/config/env.js";
5
+ {{/if}}
6
+ {{#if isPrisma8}}
7
+ import { definePrismaConfig } from "prisma/config";
8
+ {{#if hasMongodb}}
9
+ import { defineConfig as ormConfig } from "@prisma/orm-mongo/config";
10
+ {{else}}
11
+ import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";
12
+ {{/if}}
13
+
14
+ export default definePrismaConfig({
15
+ skills: {
16
+ agents: ["claude", "cursor", "agents", "devin"],
17
+ },
18
+ orm: ormConfig({
19
+ contract: "./src/prisma/contract.prisma",
20
+ db: {
21
+ connection: {{#if hasMongodb}}env.MONGODB_URL{{else}}env.POSTGRES_URL{{/if}},
22
+ },
23
+ }),
24
+ });
25
+ {{else}}
26
+ import { defineConfig } from "prisma/config";
3
27
 
4
28
  export default defineConfig({
5
- schema: "src/database/orm/prisma/schema.prisma",
29
+ schema: "src/database/orm/prisma{{#unless isBetterAuth}}/schema.prisma{{/unless}}",
6
30
  datasource: {
7
- {{#if hasPostgres}}url: env("POSTGRES_URL"),{{/if}}
8
- {{#if hasMysql}}url: env("MYSQL_URL"),{{/if}}
9
- {{#if hasSqlite}}url: env("SQLITE_PATH"),{{/if}}
31
+ {{#if hasPostgres}}url: env.POSTGRES_URL,{{/if}}
32
+ {{#if hasMysql}}url: env.MYSQL_URL,{{/if}}
33
+ {{#if hasSqlite}}url: env.SQLITE_PATH,{{/if}}
34
+ {{#if hasMongodb}}url: env.MONGODB_URL,{{/if}}
10
35
  },
11
36
  });
37
+ {{/if}}
@@ -1,22 +1,15 @@
1
1
  // oxlint-disable vitest/require-mock-type-parameters
2
2
  import type { TestingModule } from "@nestjs/testing";
3
3
  import { Test } from "@nestjs/testing";
4
- import { beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { beforeEach, describe, expect, it } from "vitest";
5
5
 
6
6
  import { AppController } from "./app.controller.js";
7
7
  import { AppService } from "./app.service.js";
8
8
  {{#if (or hasDatabase hasOrm)}}
9
- import type { ISqlAdapter } from "./database/adapters/interfaces/db-adapter.interface.js";
10
9
  import { DbService } from "./database/db.service.js";
11
10
  {{/if}}
12
11
 
13
- {{#if (or hasDatabase hasOrm)}}
14
- const mockSqlAdapter: ISqlAdapter = {
15
- execute: vi.fn().mockResolvedValue(null),
16
- getClient: vi.fn().mockReturnValue({}),
17
- query: vi.fn().mockResolvedValue([]),
18
- };
19
- {{/if}}
12
+
20
13
 
21
14
  describe(AppController, () => {
22
15
  let appController: AppController;
@@ -27,8 +20,7 @@ describe(AppController, () => {
27
20
  providers: [
28
21
  AppService,
29
22
  {{#if (or hasDatabase hasOrm)}}
30
- DbService,
31
- { provide: "SQL_ADAPTER", useValue: mockSqlAdapter },
23
+ { provide: DbService, useValue: {} },
32
24
  {{/if}}
33
25
  ],
34
26
  }).compile();
@@ -19,10 +19,10 @@ export class AppController {
19
19
  {{/if}}
20
20
  {{#if swagger}}
21
21
  @ApiOperation({ summary: "/api" })
22
- @ApiResponse({ status: 200, description: "Returns a hello world message" })
22
+ @ApiResponse({ description: "Returns a hello world message", status: 200 })
23
23
  {{/if}}
24
24
  @Get()
25
25
  getHello(): string {
26
26
  return this.appService.getHello();
27
27
  }
28
- }
28
+ }
@@ -1,5 +1,8 @@
1
- import { Module } from "@nestjs/common";
2
- {{#if (and hasAuth hasDatabase)}}
1
+ import { Module{{#if (and hasGraphql hasRateLimiting)}}, Injectable, type ExecutionContext{{/if}} } from "@nestjs/common";
2
+ {{#if (and hasGraphql hasRateLimiting)}}
3
+ import { GqlExecutionContext } from "@nestjs/graphql";
4
+ {{/if}}
5
+ {{#if (and isPassportAuth hasDatabase)}}
3
6
  import { PassportModule } from "@nestjs/passport";
4
7
  {{/if}}
5
8
  {{#if (or hasDatabase hasOrm)}}
@@ -9,10 +12,14 @@ import { DbModule } from "./database/db.module.js";
9
12
  {{#if shouldImportAuthModule}}
10
13
  import { AuthModule } from "./auth/auth.module.js";
11
14
  {{/if}}
12
- {{#if (and hasAuth hasDatabase)}}
15
+ {{#if (and isPassportAuth hasDatabase)}}
16
+ {{#if hasGraphql}}
13
17
  import { GqlAuthGuard } from "./auth/guards/gql-auth.guard.js";
18
+ {{/if}}
19
+ {{#if hasRest}}
14
20
  import { RestAuthGuard } from "./auth/guards/rest-auth.guard.js";
15
21
  {{/if}}
22
+ {{/if}}
16
23
  {{#if hasGraphql}}
17
24
  import { GraphqlAppModule } from "./graphql/graphql.module.js";
18
25
  {{/if}}
@@ -24,6 +31,9 @@ import { AppErrorHandler } from "./libs/trpc/error.handler.js";
24
31
  {{#if hasTrpcAndAuth}}
25
32
  import { AuthTRPCMiddleware } from "./libs/middlewares/auth.trpc.middleware.js";
26
33
  {{/if}}
34
+ {{#if (and hasTrpc hasRateLimiting)}}
35
+ import { RateLimitTRPCMiddleware } from "./libs/middlewares/rate-limit.trpc.middleware.js";
36
+ {{/if}}
27
37
  {{/if}}
28
38
 
29
39
  {{#if hasRest}}
@@ -35,8 +45,35 @@ import { TodoModule } from "./api/todo/todo.module.js";
35
45
  {{#if (and hasTrpc hasTodoExample hasRest)}}
36
46
  import { TodoRouter } from "./api/todo/todo.router.js";
37
47
  {{/if}}
48
+ {{#if hasHealthCheck}}
49
+ import { HealthModule } from "./health/health.module.js";
50
+ {{/if}}
51
+ {{#if hasRateLimiting}}
52
+ import { APP_GUARD } from "@nestjs/core";
53
+ import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
54
+ {{/if}}
55
+ {{#if (and isPassportAuth hasDatabase hasGraphql (not hasRateLimiting))}}
56
+ import { APP_GUARD } from "@nestjs/core";
57
+ {{/if}}
38
58
  import { AppService } from "./app.service.js";
39
59
 
60
+ {{#if (and hasGraphql hasRateLimiting)}}
61
+ @Injectable()
62
+ class GraphqlThrottlerGuard extends ThrottlerGuard {
63
+ protected getRequestResponse(context: ExecutionContext) {
64
+ if (context.getType<string>() !== "graphql") return super.getRequestResponse(context);
65
+ const gqlContext = GqlExecutionContext.create(context).getContext();
66
+ return { req: gqlContext.req, res: { header: () => undefined } };
67
+ }
68
+
69
+ protected getTracker(request: Record<string, unknown>): Promise<string> {
70
+ const socket = request.socket as { remoteAddress?: string } | undefined;
71
+ const ip = request.ip;
72
+ return Promise.resolve(typeof ip === "string" ? ip : socket?.remoteAddress ?? "unknown");
73
+ }
74
+ }
75
+ {{/if}}
76
+
40
77
  @Module({
41
78
  {{#if hasRest}}
42
79
  controllers: [
@@ -55,7 +92,7 @@ import { AppService } from "./app.service.js";
55
92
  {{#if shouldImportAuthModule}}
56
93
  AuthModule,
57
94
  {{/if}}
58
- {{#if (and hasAuth hasDatabase)}}
95
+ {{#if (and isPassportAuth hasDatabase)}}
59
96
  PassportModule.register({ defaultStrategy: "jwt" }),
60
97
  {{/if}}
61
98
  {{#if hasGraphql}}
@@ -65,22 +102,46 @@ import { AppService } from "./app.service.js";
65
102
  TRPCModule.forRoot({
66
103
  context: AppContext,
67
104
  onError: AppErrorHandler,
105
+ {{#if hasRateLimiting}}
106
+ globalMiddlewares: [RateLimitTRPCMiddleware],
107
+ {{/if}}
68
108
  }),
69
109
  {{/if}}
70
110
  {{#if (and hasTodoExample (or hasRest hasTrpc))}}
71
111
  TodoModule,
72
112
  {{/if}}
113
+ {{#if hasHealthCheck}}
114
+ HealthModule,
115
+ {{/if}}
116
+ {{#if hasRateLimiting}}
117
+ ThrottlerModule.forRoot([
118
+ {
119
+ ttl: 60_000,
120
+ limit: 60,
121
+ {{#if hasGraphql}}
122
+ setHeaders: false,
123
+ {{/if}}
124
+ },
125
+ ]),
126
+ {{/if}}
73
127
  ],
74
128
  providers: [
75
129
  AppService,
76
- {{#if (and hasAuth hasDatabase)}}
130
+ {{#if (and isPassportAuth hasDatabase)}}
131
+ {{#if hasGraphql}}
77
132
  GqlAuthGuard,
133
+ {{/if}}
134
+ {{#if hasRest}}
78
135
  RestAuthGuard,
79
136
  {{/if}}
137
+ {{/if}}
80
138
  {{#if hasTrpc}}
81
139
  {{#if hasTrpcAndAuth}}
82
140
  AuthTRPCMiddleware,
83
141
  {{/if}}
142
+ {{#if hasRateLimiting}}
143
+ RateLimitTRPCMiddleware,
144
+ {{/if}}
84
145
  AppContext,
85
146
  AppErrorHandler,
86
147
  AppRouter,
@@ -88,6 +149,16 @@ import { AppService } from "./app.service.js";
88
149
  TodoRouter,
89
150
  {{/if}}
90
151
  {{/if}}
152
+ {{#if hasRateLimiting}}
153
+ {
154
+ provide: APP_GUARD,
155
+ {{#if hasGraphql}}
156
+ useClass: GraphqlThrottlerGuard,
157
+ {{else}}
158
+ useClass: ThrottlerGuard,
159
+ {{/if}}
160
+ },
161
+ {{/if}}
91
162
  ],
92
163
  })
93
164
  export class AppModule {}
@@ -1,7 +1,6 @@
1
- import { Router, Query, UseMiddlewares, Ctx } from "nestjs-trpc";
2
- import z from "zod";
1
+ import { Router, Query{{#if hasTrpcAndAuth}}, UseMiddlewares, Ctx{{/if}} } from "nestjs-trpc";
2
+ import { z } from "zod";
3
3
  {{#if hasTrpcAndAuth}}
4
- import type { UserSession } from "./auth/auth.js";
5
4
  import { AuthTRPCMiddleware } from "./libs/middlewares/auth.trpc.middleware.js";
6
5
  import type { InnerContext } from "./libs/trpc/inner.context.js";
7
6
  {{/if}}
@@ -14,7 +13,7 @@ export class AppRouterClass {
14
13
  {{#if hasTrpcAndAuth}}
15
14
  @Query()
16
15
  @UseMiddlewares(AuthTRPCMiddleware)
17
- testAuth(@Ctx() ctx: InnerContext): UserSession {
16
+ testAuth(@Ctx() ctx: InnerContext): NonNullable<InnerContext["session"]> {
18
17
  return ctx.session!;
19
18
  }
20
19
 
@@ -7,8 +7,15 @@ export interface ISqlAdapter<TClient = unknown> {
7
7
  {{/if}}
8
8
 
9
9
  {{#if hasMongodb}}
10
+ {{#if useRawSql}}
11
+ import type { Collection, Document } from "mongodb";
12
+ {{/if}}
10
13
  export interface IMongoAdapter<TClient = unknown> {
14
+ {{#if useRawSql}}
15
+ collection<T extends Document = Document>(name: string): Collection<T>;
16
+ {{else}}
11
17
  collection<T = unknown>(name: string): T;
18
+ {{/if}}
12
19
  getClient(): TClient;
13
20
  }
14
21
  {{/if}}
@@ -1,13 +1,44 @@
1
1
  import { Injectable, Inject } from '@nestjs/common';
2
2
  import { IMongoAdapter } from '../interfaces/db-adapter.interface.js';
3
+ {{#if isPrisma8}}
3
4
  {{#if isMonorepo}}
4
- import type { PrismaClient } from '../../../database/orm/prisma/generated/client.js';
5
+ import { db } from '../../../prisma/db.js';
5
6
  {{else}}
6
- import type { PrismaClient } from '../../orm/prisma/generated/client.js';
7
+ import { db } from '../../prisma/db.js';
8
+ {{/if}}
9
+ {{else}}
10
+ {{#if isMonorepo}}
11
+ {{#if isPrisma6}}
12
+ import type { Prisma, PrismaClient } from '@prisma/client';
13
+ {{else}}
14
+ import type { Prisma, PrismaClient } from '../../database/orm/prisma/generated/client.js';
15
+ {{/if}}
16
+ {{else}}
17
+ {{#if isPrisma6}}
18
+ import type { Prisma, PrismaClient } from '@prisma/client';
19
+ {{else}}
20
+ import type { Prisma, PrismaClient } from '../../orm/prisma/generated/client.js';
21
+ {{/if}}
22
+ {{/if}}
7
23
  {{/if}}
8
24
 
9
25
  @Injectable()
10
- export class PrismaMongoAdapter implements IMongoAdapter<PrismaClient> {
26
+ export class PrismaMongoAdapter implements IMongoAdapter<any> {
27
+ {{#if isPrisma8}}
28
+ readonly client = db;
29
+
30
+ collection<T = unknown>(name: string): T {
31
+ return ((this.client as any).orm as any)?.[name] as T;
32
+ }
33
+
34
+ async runCommand(command: unknown): Promise<unknown> {
35
+ return (this.client as any).query?.(command);
36
+ }
37
+
38
+ getClient() {
39
+ return this.client;
40
+ }
41
+ {{else}}
11
42
  constructor(
12
43
  @Inject('PRISMA_MONGO_CLIENT') private readonly prisma: PrismaClient
13
44
  ) {}
@@ -15,11 +46,12 @@ export class PrismaMongoAdapter implements IMongoAdapter<PrismaClient> {
15
46
  return this.prisma[name as keyof PrismaClient] as T;
16
47
  }
17
48
 
18
- async runCommand(command: unknown): Promise<unknown> {
49
+ async runCommand(command: Prisma.InputJsonObject): Promise<unknown> {
19
50
  return this.prisma.$runCommandRaw(command);
20
51
  }
21
52
 
22
53
  getClient(): PrismaClient {
23
54
  return this.prisma;
24
55
  }
56
+ {{/if}}
25
57
  }
@@ -1,17 +1,22 @@
1
- import { Injectable, Inject } from '@nestjs/common';
2
- import { IMongoAdapter } from '../interfaces/db-adapter.interface.js';
1
+ import { Injectable, Inject, type OnModuleInit, type OnModuleDestroy } from "@nestjs/common";
2
+ import { MongoClient, type Collection, type Db, type Document } from "mongodb";
3
+ import type { IMongoAdapter } from "../interfaces/db-adapter.interface.js";
3
4
 
4
5
  @Injectable()
5
- export class RawMongoAdapter implements IMongoAdapter {
6
- constructor(
7
- @Inject('MONGODB_CLIENT') private readonly db: any
8
- ) {}
9
-
10
- collection(name: string): any {
11
- return this.db.collection(name);
6
+ export class RawMongoAdapter implements IMongoAdapter<Db>, OnModuleInit, OnModuleDestroy {
7
+ constructor(@Inject("MONGODB_CLIENT") private readonly client: MongoClient) {}
8
+ collection<T extends Document = Document>(name: string): Collection<T> {
9
+ return this.client.db().collection<T>(name);
12
10
  }
13
-
14
- getClient(): any {
15
- return this.db;
11
+ getClient(): Db { return this.client.db(); }
12
+ async onModuleInit(): Promise<void> {
13
+ try {
14
+ await this.client.connect();
15
+ await this.client.db().command({ ping: 1 });
16
+ } catch (error) {
17
+ await this.client.close();
18
+ throw error;
19
+ }
16
20
  }
21
+ async onModuleDestroy(): Promise<void> { await this.client.close(); }
17
22
  }
@@ -21,4 +21,13 @@ export class DrizzleSqlAdapter<TClient = unknown> implements ISqlAdapter<TClient
21
21
  getClient(): TClient {
22
22
  return this.db;
23
23
  }
24
+
25
+ async onModuleDestroy(): Promise<void> {
26
+ const client = (this.db as any).$client;
27
+ if (typeof client?.end === 'function') {
28
+ await client.end();
29
+ } else if (typeof client?.close === 'function') {
30
+ await client.close();
31
+ }
32
+ }
24
33
  }