ginskill-init 1.0.0

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 +77 -0
  2. package/agents/developer.md +56 -0
  3. package/agents/frontend-design.md +69 -0
  4. package/agents/mobile-reviewer.md +36 -0
  5. package/agents/review-code.md +49 -0
  6. package/agents/security-scanner.md +50 -0
  7. package/agents/tester.md +72 -0
  8. package/bin/cli.js +226 -0
  9. package/package.json +20 -0
  10. package/skills/ai-asset-generator/SKILL.md +255 -0
  11. package/skills/ai-asset-generator/docs/gen-image.md +274 -0
  12. package/skills/ai-asset-generator/docs/genvideo.md +341 -0
  13. package/skills/ai-asset-generator/docs/remove-background.md +19 -0
  14. package/skills/ai-asset-generator/generate-credit-assets.mjs +180 -0
  15. package/skills/ai-asset-generator/generate-ginbrowser-assets.mjs +242 -0
  16. package/skills/ai-asset-generator/generate-sty-icon.mjs +149 -0
  17. package/skills/ai-asset-generator/lib/bg-remove.mjs +34 -0
  18. package/skills/ai-asset-generator/lib/env.mjs +38 -0
  19. package/skills/ai-asset-generator/lib/kie-client.mjs +88 -0
  20. package/skills/ai-asset-generator/scripts/scaffold-generator.mjs +203 -0
  21. package/skills/ai-build-ai/SKILL.md +124 -0
  22. package/skills/ai-build-ai/docs/agent-teams.md +293 -0
  23. package/skills/ai-build-ai/docs/checkpointing.md +161 -0
  24. package/skills/ai-build-ai/docs/create-agent.md +399 -0
  25. package/skills/ai-build-ai/docs/create-mcp.md +395 -0
  26. package/skills/ai-build-ai/docs/create-skill.md +299 -0
  27. package/skills/ai-build-ai/docs/headless-mode.md +614 -0
  28. package/skills/ai-build-ai/docs/hooks.md +578 -0
  29. package/skills/ai-build-ai/docs/memory-claude-md.md +375 -0
  30. package/skills/ai-build-ai/docs/output-styles.md +208 -0
  31. package/skills/ai-build-ai/docs/overview.md +162 -0
  32. package/skills/ai-build-ai/docs/permissions.md +391 -0
  33. package/skills/ai-build-ai/docs/plugins.md +396 -0
  34. package/skills/ai-build-ai/docs/sandbox.md +262 -0
  35. package/skills/ai-build-ai/scripts/load-tutorial.sh +54 -0
  36. package/skills/icon-generator/SKILL.md +270 -0
  37. package/skills/mobile-app-review/SKILL.md +321 -0
  38. package/skills/mobile-app-review/references/apple-review.md +132 -0
  39. package/skills/mobile-app-review/references/google-play-review.md +203 -0
  40. package/skills/mongodb/SKILL.md +667 -0
  41. package/skills/mongodb/references/mongoose-patterns.md +368 -0
  42. package/skills/nestjs-architecture/SKILL.md +1086 -0
  43. package/skills/nestjs-architecture/references/advanced-patterns.md +590 -0
  44. package/skills/performance/SKILL.md +509 -0
  45. package/skills/react-fsd-architecture/SKILL.md +693 -0
  46. package/skills/react-fsd-architecture/references/fsd-patterns.md +747 -0
  47. package/skills/react-query/SKILL.md +685 -0
  48. package/skills/react-query/references/query-patterns.md +365 -0
  49. package/skills/review-code/SKILL.md +321 -0
  50. package/skills/review-code/references/clean-code-principles.md +395 -0
  51. package/skills/review-code/references/frontend-patterns.md +136 -0
  52. package/skills/review-code/references/nestjs-patterns.md +184 -0
  53. package/skills/review-code/scripts/check-module.sh +201 -0
  54. package/skills/review-code/scripts/deep-scan.sh +604 -0
  55. package/skills/review-code/scripts/dep-check.sh +522 -0
  56. package/skills/review-code/scripts/detect-duplicates.sh +466 -0
  57. package/skills/review-code/scripts/format-check.sh +577 -0
  58. package/skills/review-code/scripts/run-review.sh +167 -0
  59. package/skills/review-code/scripts/scan-codebase.sh +152 -0
  60. package/skills/security-scanner/SKILL.md +327 -0
  61. package/skills/security-scanner/references/nestjs-security.md +260 -0
  62. package/skills/security-scanner/references/nextjs-security.md +201 -0
  63. package/skills/security-scanner/references/react-native-security.md +199 -0
  64. package/skills/security-scanner/scripts/security-scan.sh +478 -0
  65. package/skills/ui-ux-pro-max/SKILL.md +377 -0
  66. package/skills/ui-ux-pro-max/data/charts.csv +26 -0
  67. package/skills/ui-ux-pro-max/data/colors.csv +97 -0
  68. package/skills/ui-ux-pro-max/data/icons.csv +101 -0
  69. package/skills/ui-ux-pro-max/data/landing.csv +31 -0
  70. package/skills/ui-ux-pro-max/data/products.csv +97 -0
  71. package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  72. package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  73. package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  74. package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  75. package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  76. package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  77. package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  78. package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  79. package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  80. package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  81. package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  82. package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  83. package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  84. package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  85. package/skills/ui-ux-pro-max/data/styles.csv +68 -0
  86. package/skills/ui-ux-pro-max/data/typography.csv +58 -0
  87. package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  88. package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  89. package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  90. package/skills/ui-ux-pro-max/scripts/core.py +253 -0
  91. package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  92. package/skills/ui-ux-pro-max/scripts/search.py +114 -0
@@ -0,0 +1,1086 @@
1
+ ---
2
+ name: nestjs-architecture
3
+ description: |
4
+ **NestJS Feature-Based Architecture**: Production patterns for organizing NestJS backends — feature modules, core infrastructure, shared utilities, guards, queues, events, error handling, and project structure.
5
+ - MANDATORY TRIGGERS: nestjs architecture, nestjs structure, nestjs module, nestjs feature module, nestjs project structure, nestjs folder structure, nestjs organize, nestjs boilerplate, nestjs scaffold, nestjs guard, nestjs interceptor, nestjs pipe, nestjs filter, nestjs exception, nestjs queue, nestjs event, nestjs event emitter, nestjs bull, nestjs redis, nestjs config, nestjs database module, nestjs health check, nestjs rate limit, nestjs middleware, nestjs core module, nestjs shared module, nestjs dto pattern, nestjs service pattern, nestjs controller pattern, nestjs processor, nestjs decorator, nestjs graceful shutdown, nestjs swagger setup, nestjs validation, nestjs monolith
6
+ - Use this skill whenever the user is setting up a NestJS project, creating new feature modules, organizing backend code, adding infrastructure (guards, queues, events, health checks), or reviewing NestJS architecture. Also trigger when discussing backend project structure, module boundaries, dependency injection patterns, or scaling NestJS applications.
7
+ ---
8
+
9
+ # NestJS Feature-Based Architecture
10
+
11
+ Production-ready patterns for organizing NestJS backends using a feature-based modular structure. Covers project layout, core infrastructure, feature modules, shared utilities, guards, queues, events, error handling, and scaling.
12
+
13
+ ## Core Mental Model
14
+
15
+ **Features are the organizing principle.** Group code by business domain (user, order, notification), not by technical layer (controllers/, services/, entities/). Each feature is a self-contained module that owns its routes, business logic, data access, DTOs, and schemas.
16
+
17
+ Key principles:
18
+ - A feature module should be deletable without breaking unrelated features
19
+ - Core infrastructure (database, cache, auth, logging) lives in `core/` — imported once at the root
20
+ - Shared utilities (decorators, pipes, DTOs) live in `shared/` — imported where needed
21
+ - Communication between features uses events, not direct service imports
22
+ - Every module explicitly declares its imports and exports
23
+
24
+ ## Project Structure
25
+
26
+ ```
27
+ src/
28
+ ├── main.ts # Bootstrap, global pipes/filters/interceptors
29
+ ├── app.module.ts # Root module — imports core + features
30
+ ├── app.controller.ts # Root health/info endpoint
31
+ ├── app.service.ts # Root service
32
+
33
+ ├── core/ # Platform infrastructure (imported once)
34
+ │ ├── config/ # Environment & app configuration
35
+ │ │ ├── env.schema.ts # Zod/Joi validation for env vars
36
+ │ │ ├── app.config.ts # Static app constants
37
+ │ │ ├── jwt.config.ts # Token expiration settings
38
+ │ │ ├── cors.config.ts # CORS origins
39
+ │ │ ├── helmet.config.ts # Security headers
40
+ │ │ └── swagger.config.ts # API documentation setup
41
+ │ ├── database/ # Database connection & lifecycle
42
+ │ │ ├── database.module.ts # Mongoose/TypeORM connection
43
+ │ │ └── database-cleanup.service.ts
44
+ │ ├── redis/ # Cache & session management
45
+ │ │ ├── redis.module.ts # Global Redis provider
46
+ │ │ ├── redis.service.ts # Redis client wrapper
47
+ │ │ └── redis.constants.ts
48
+ │ ├── queue/ # Job queue infrastructure
49
+ │ │ ├── queue.module.ts # Bull/BullMQ configuration
50
+ │ │ ├── base-queue.service.ts # Abstract queue service
51
+ │ │ ├── base-processor.ts # Abstract job processor
52
+ │ │ └── queue.constants.ts # Retry, backoff, timeout defaults
53
+ │ ├── logger/ # Structured logging
54
+ │ │ └── logger.module.ts
55
+ │ ├── health/ # Health check endpoints
56
+ │ │ ├── health.controller.ts # /health endpoint
57
+ │ │ └── custom-disk.indicator.ts
58
+ │ ├── exception/ # Global error handling
59
+ │ │ ├── http-exception.filter.ts # Global exception filter
60
+ │ │ └── app.exception.ts # Custom exception hierarchy
61
+ │ └── scheduler/ # Cron job registration
62
+ │ ├── scheduler.module.ts
63
+ │ └── scheduler.service.ts
64
+
65
+ ├── features/ # Business domain modules
66
+ │ ├── user/
67
+ │ │ ├── user.module.ts
68
+ │ │ ├── user.controller.ts
69
+ │ │ ├── user.service.ts
70
+ │ │ ├── user.event.ts # Event listeners
71
+ │ │ ├── dto/
72
+ │ │ │ ├── create-user.dto.ts
73
+ │ │ │ └── update-user.dto.ts
74
+ │ │ ├── entities/
75
+ │ │ │ └── user.schema.ts # Mongoose schema
76
+ │ │ ├── processors/ # Queue job handlers
77
+ │ │ │ └── user-deletion.processor.ts
78
+ │ │ └── services/ # Feature-specific sub-services
79
+ │ │ └── user-profile.service.ts
80
+ │ ├── auth/
81
+ │ ├── order/
82
+ │ ├── notification/
83
+ │ ├── media/
84
+ │ └── ...
85
+
86
+ ├── shared/ # Cross-feature utilities
87
+ │ ├── decorators/ # Custom decorators
88
+ │ │ ├── pagination.decorator.ts
89
+ │ │ └── api-select.decorator.ts
90
+ │ ├── guards/ # Reusable guards
91
+ │ │ └── rate-limit.guard.ts
92
+ │ ├── pipes/ # Transform/validation pipes
93
+ │ │ ├── populate.pipe.ts
94
+ │ │ ├── select.pipe.ts
95
+ │ │ └── condition.pipe.ts
96
+ │ ├── dto/ # Shared DTOs
97
+ │ │ ├── pagination.dto.ts
98
+ │ │ └── delete-response.dto.ts
99
+ │ ├── schema/ # Shared schema mixins
100
+ │ │ ├── priority.schema.ts
101
+ │ │ └── thumbnail.schema.ts
102
+ │ ├── enum/ # Shared enumerations
103
+ │ ├── events/ # Domain event types
104
+ │ │ └── domain-event.ts
105
+ │ ├── utils/ # Pure utility functions
106
+ │ ├── validators/ # Custom class-validator rules
107
+ │ └── interfaces/ # Shared type interfaces
108
+
109
+ └── types/ # Global type declarations
110
+ ```
111
+
112
+ ## Core Infrastructure
113
+
114
+ ### Config: Environment Validation with Zod
115
+
116
+ Validate **all** environment variables at startup. The app should crash immediately if config is invalid — not at runtime when a feature first reads a missing var.
117
+
118
+ ```typescript
119
+ // core/config/env.schema.ts
120
+ import { z } from 'zod'
121
+
122
+ export const envSchema = z.object({
123
+ // Server
124
+ NODE_ENV: z.enum(['development', 'staging', 'production']).default('development'),
125
+ PORT: z.coerce.number().default(3000),
126
+
127
+ // Database
128
+ MONGODB_CONNECTION_STRING: z.string().url(),
129
+ MONGODB_NAME: z.string().min(1),
130
+
131
+ // Redis
132
+ REDIS_HOST: z.string().default('localhost'),
133
+ REDIS_PORT: z.coerce.number().default(6379),
134
+ REDIS_PASSWORD: z.string().optional(),
135
+
136
+ // JWT
137
+ JWT_ACCESS_TOKEN_SECRET: z.string().min(32),
138
+ JWT_REFRESH_TOKEN_SECRET: z.string().min(32),
139
+ JWT_ACCESS_TOKEN_EXPIRY: z.string().default('15m'),
140
+ JWT_REFRESH_TOKEN_EXPIRY: z.string().default('7d'),
141
+
142
+ // Storage — conditional validation
143
+ STORAGE_TYPE: z.enum(['local', 's3']).default('local'),
144
+ AWS_S3_BUCKET: z.string().optional(),
145
+ AWS_S3_REGION: z.string().optional(),
146
+ AWS_ACCESS_KEY_ID: z.string().optional(),
147
+ AWS_SECRET_ACCESS_KEY: z.string().optional(),
148
+
149
+ // Feature flags
150
+ SWAGGER_ENABLED: z.coerce.boolean().default(true),
151
+ ENABLE_TERMINAL_LOGGER: z.coerce.boolean().default(true),
152
+ ENABLE_FILE_LOGGER: z.coerce.boolean().default(false),
153
+ }).refine(
154
+ (data) => data.STORAGE_TYPE !== 's3' || (data.AWS_S3_BUCKET && data.AWS_S3_REGION),
155
+ { message: 'AWS_S3_BUCKET and AWS_S3_REGION required when STORAGE_TYPE=s3' },
156
+ )
157
+
158
+ export type EnvConfig = z.infer<typeof envSchema>
159
+ ```
160
+
161
+ ```typescript
162
+ // main.ts — validate on startup
163
+ import { envSchema } from './core/config/env.schema'
164
+
165
+ const env = envSchema.safeParse(process.env)
166
+ if (!env.success) {
167
+ console.error('Invalid environment variables:', env.error.format())
168
+ process.exit(1)
169
+ }
170
+ ```
171
+
172
+ ### Database Module
173
+
174
+ ```typescript
175
+ // core/database/database.module.ts
176
+ import { Module, Logger } from '@nestjs/common'
177
+ import { MongooseModule } from '@nestjs/mongoose'
178
+ import { ConfigService } from '@nestjs/config'
179
+ import mongoose from 'mongoose'
180
+
181
+ @Module({
182
+ imports: [
183
+ MongooseModule.forRootAsync({
184
+ inject: [ConfigService],
185
+ useFactory: (config: ConfigService) => {
186
+ const logger = new Logger('Database')
187
+
188
+ mongoose.connection.on('connecting', () => logger.log('Connecting to MongoDB...'))
189
+ mongoose.connection.on('connected', () => logger.log('MongoDB connected'))
190
+ mongoose.connection.on('error', (err) => logger.error('MongoDB error:', err))
191
+ mongoose.connection.on('disconnected', () => logger.warn('MongoDB disconnected'))
192
+ mongoose.connection.on('reconnected', () => logger.log('MongoDB reconnected'))
193
+
194
+ return {
195
+ uri: config.get('MONGODB_CONNECTION_STRING'),
196
+ dbName: config.get('MONGODB_NAME'),
197
+ maxPoolSize: 10,
198
+ minPoolSize: 2,
199
+ serverSelectionTimeoutMS: 5000,
200
+ socketTimeoutMS: 45000,
201
+ }
202
+ },
203
+ }),
204
+ ],
205
+ })
206
+ export class DatabaseModule {}
207
+ ```
208
+
209
+ ### Global Exception Filter
210
+
211
+ ```typescript
212
+ // core/exception/app.exception.ts
213
+ export class AppException extends Error {
214
+ constructor(
215
+ public readonly errorCode: string,
216
+ public readonly statusCode: number,
217
+ message: string,
218
+ public readonly details?: Record<string, any>,
219
+ ) {
220
+ super(message)
221
+ }
222
+
223
+ static from(error: unknown): AppException {
224
+ if (error instanceof AppException) return error
225
+ if (error instanceof HttpException) {
226
+ return new AppException('HTTP_ERROR', error.getStatus(), error.message)
227
+ }
228
+ return new AppException('INTERNAL_ERROR', 500, 'An unexpected error occurred')
229
+ }
230
+ }
231
+
232
+ export class UnauthorizedException extends AppException {
233
+ constructor(message = 'Unauthorized') {
234
+ super('UNAUTHORIZED', 401, message)
235
+ }
236
+ }
237
+
238
+ export class NotFoundException extends AppException {
239
+ constructor(entity: string, id?: string) {
240
+ super('NOT_FOUND', 404, id ? `${entity} with id ${id} not found` : `${entity} not found`)
241
+ }
242
+ }
243
+
244
+ export class ValidationException extends AppException {
245
+ constructor(details: Record<string, any>) {
246
+ super('VALIDATION_ERROR', 422, 'Validation failed', details)
247
+ }
248
+ }
249
+ ```
250
+
251
+ ```typescript
252
+ // core/exception/http-exception.filter.ts
253
+ @Catch()
254
+ export class HttpExceptionFilter implements ExceptionFilter {
255
+ private readonly logger = new Logger('ExceptionFilter')
256
+
257
+ catch(exception: unknown, host: ArgumentsHost) {
258
+ const ctx = host.switchToHttp()
259
+ const response = ctx.getResponse<Response>()
260
+ const request = ctx.getRequest<Request>()
261
+
262
+ const appException = AppException.from(exception)
263
+ const isProd = process.env.NODE_ENV === 'production'
264
+
265
+ this.logger.error(`[${appException.errorCode}] ${appException.message}`, {
266
+ path: request.url,
267
+ method: request.method,
268
+ ...(isProd ? {} : { stack: (exception as Error)?.stack }),
269
+ })
270
+
271
+ response.status(appException.statusCode).json({
272
+ error_code: appException.errorCode,
273
+ message: appException.message,
274
+ details: appException.details,
275
+ ...(isProd ? {} : { stack: (exception as Error)?.stack }),
276
+ })
277
+ }
278
+ }
279
+ ```
280
+
281
+ ### Queue Infrastructure
282
+
283
+ ```typescript
284
+ // core/queue/queue.constants.ts
285
+ export const QUEUE_DEFAULTS = {
286
+ attempts: 3,
287
+ backoff: { type: 'exponential' as const, delay: 2000 },
288
+ timeout: 30_000,
289
+ removeOnComplete: { count: 100 },
290
+ removeOnFail: { count: 500 },
291
+ }
292
+ ```
293
+
294
+ ```typescript
295
+ // core/queue/base-queue.service.ts
296
+ import { Queue, Job } from 'bull'
297
+
298
+ export abstract class BaseQueueService {
299
+ private dedupeCache = new Map<string, number>()
300
+
301
+ constructor(protected readonly queue: Queue) {}
302
+
303
+ async addJob<T>(name: string, data: T, opts?: { dedupeKey?: string; dedupeTtl?: number }) {
304
+ // Deduplication — prevent identical jobs within TTL window
305
+ if (opts?.dedupeKey) {
306
+ const lastRun = this.dedupeCache.get(opts.dedupeKey)
307
+ const ttl = opts.dedupeTtl ?? 60_000
308
+ if (lastRun && Date.now() - lastRun < ttl) return null
309
+ this.dedupeCache.set(opts.dedupeKey, Date.now())
310
+ }
311
+
312
+ return this.queue.add(name, data, {
313
+ ...QUEUE_DEFAULTS,
314
+ })
315
+ }
316
+
317
+ async getJobStatus(jobId: string) {
318
+ const job = await this.queue.getJob(jobId)
319
+ if (!job) return null
320
+ const state = await job.getState()
321
+ return { id: job.id, state, data: job.data, progress: job.progress() }
322
+ }
323
+
324
+ async getMetrics() {
325
+ const [completed, failed, waiting, active] = await Promise.all([
326
+ this.queue.getCompletedCount(),
327
+ this.queue.getFailedCount(),
328
+ this.queue.getWaitingCount(),
329
+ this.queue.getActiveCount(),
330
+ ])
331
+ return { completed, failed, waiting, active }
332
+ }
333
+
334
+ async gracefulShutdown(timeoutMs = 30_000) {
335
+ await this.queue.pause(true)
336
+ const start = Date.now()
337
+ while (Date.now() - start < timeoutMs) {
338
+ const active = await this.queue.getActiveCount()
339
+ if (active === 0) break
340
+ await new Promise((r) => setTimeout(r, 1000))
341
+ }
342
+ await this.queue.close()
343
+ }
344
+ }
345
+ ```
346
+
347
+ ### Health Checks
348
+
349
+ ```typescript
350
+ // core/health/health.controller.ts
351
+ @Controller('health')
352
+ export class HealthController {
353
+ constructor(
354
+ private health: HealthCheckService,
355
+ private mongoose: MongooseHealthIndicator,
356
+ private disk: DiskHealthIndicator,
357
+ private memory: MemoryHealthIndicator,
358
+ ) {}
359
+
360
+ @Get()
361
+ @HealthCheck()
362
+ check() {
363
+ const isProd = process.env.NODE_ENV === 'production'
364
+ return this.health.check([
365
+ () => this.mongoose.pingCheck('mongodb'),
366
+ () => this.disk.checkStorage('disk', {
367
+ path: '/',
368
+ thresholdPercent: isProd ? 0.75 : 0.90,
369
+ }),
370
+ () => this.memory.checkHeap('memory_heap', 300 * 1024 * 1024),
371
+ ])
372
+ }
373
+ }
374
+ ```
375
+
376
+ ### Rate Limiting with Redis
377
+
378
+ ```typescript
379
+ // shared/guards/rate-limit.guard.ts
380
+ @Injectable()
381
+ export class RateLimitGuard implements CanActivate {
382
+ constructor(private readonly redisService: RedisService) {}
383
+
384
+ async canActivate(context: ExecutionContext): Promise<boolean> {
385
+ const request = context.switchToHttp().getRequest()
386
+ const userId = request.user?.id ?? this.getGuestId(request)
387
+ const key = `rate:${userId}`
388
+ const window = 60 // seconds
389
+ const maxRequests = 100
390
+
391
+ // Atomic sliding window via Lua script
392
+ const script = `
393
+ local key = KEYS[1]
394
+ local now = tonumber(ARGV[1])
395
+ local window = tonumber(ARGV[2])
396
+ local max = tonumber(ARGV[3])
397
+ redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000)
398
+ local count = redis.call('ZCARD', key)
399
+ if count >= max then return 0 end
400
+ redis.call('ZADD', key, now, now .. ':' .. math.random())
401
+ redis.call('EXPIRE', key, window)
402
+ return 1
403
+ `
404
+
405
+ const allowed = await this.redisService.eval(script, 1, key, Date.now(), window, maxRequests)
406
+ if (!allowed) throw new HttpException('Too Many Requests', 429)
407
+ return true
408
+ }
409
+
410
+ private getGuestId(req: Request): string {
411
+ const ip = req.ip || req.socket.remoteAddress
412
+ const ua = req.headers['user-agent'] || ''
413
+ return `guest:${createHash('sha256').update(`${ip}:${ua}`).digest('hex').slice(0, 16)}`
414
+ }
415
+ }
416
+ ```
417
+
418
+ ## Feature Module Pattern
419
+
420
+ ### Anatomy of a Feature
421
+
422
+ Every feature module follows the same structure:
423
+
424
+ ```
425
+ features/order/
426
+ ├── order.module.ts # Module — imports, providers, exports
427
+ ├── order.controller.ts # HTTP routes
428
+ ├── order.service.ts # Business logic
429
+ ├── order.event.ts # Event listeners (@OnEvent)
430
+ ├── dto/
431
+ │ ├── create-order.dto.ts # Input validation
432
+ │ ├── update-order.dto.ts
433
+ │ └── order-response.dto.ts # Output shape
434
+ ├── entities/
435
+ │ └── order.schema.ts # Mongoose schema
436
+ ├── processors/
437
+ │ └── order-fulfillment.processor.ts # Queue job handler
438
+ └── services/
439
+ ├── order-pricing.service.ts # Sub-service for complex logic
440
+ └── order-notification.service.ts
441
+ ```
442
+
443
+ ### Module Definition
444
+
445
+ ```typescript
446
+ // features/order/order.module.ts
447
+ @Module({
448
+ imports: [
449
+ MongooseModule.forFeature([{ name: Order.name, schema: OrderSchema }]),
450
+ BullModule.registerQueue({ name: 'order-fulfillment' }),
451
+ forwardRef(() => UserModule), // circular dependency resolution
452
+ ],
453
+ controllers: [OrderController],
454
+ providers: [
455
+ OrderService,
456
+ OrderPricingService,
457
+ OrderNotificationService,
458
+ OrderFulfillmentProcessor,
459
+ ],
460
+ exports: [OrderService], // only export what other modules need
461
+ })
462
+ export class OrderModule {}
463
+ ```
464
+
465
+ ### Controller Pattern
466
+
467
+ ```typescript
468
+ // features/order/order.controller.ts
469
+ @ApiTags('Orders')
470
+ @Controller('orders')
471
+ @UseGuards(HybridAuthGuard, RolesGuard)
472
+ export class OrderController {
473
+ constructor(private readonly orderService: OrderService) {}
474
+
475
+ @Get()
476
+ @Auth(Role.USER)
477
+ @ApiPagination()
478
+ async findAll(
479
+ @CurrentUser() user: UserDocument,
480
+ @GetPagination() pagination: PaginationDto,
481
+ ) {
482
+ return this.orderService.findAll(user.id, pagination)
483
+ }
484
+
485
+ @Get(':id')
486
+ @Auth(Role.USER)
487
+ async findOne(
488
+ @CurrentUser() user: UserDocument,
489
+ @Param('id') id: string,
490
+ ) {
491
+ return this.orderService.findOneOrFail(id, user.id)
492
+ }
493
+
494
+ @Post()
495
+ @Auth(Role.USER)
496
+ async create(
497
+ @CurrentUser() user: UserDocument,
498
+ @Body() dto: CreateOrderDto,
499
+ ) {
500
+ return this.orderService.create(user.id, dto)
501
+ }
502
+
503
+ @Patch(':id')
504
+ @Auth(Role.USER)
505
+ async update(
506
+ @Param('id') id: string,
507
+ @CurrentUser() user: UserDocument,
508
+ @Body() dto: UpdateOrderDto,
509
+ ) {
510
+ return this.orderService.update(id, user.id, dto)
511
+ }
512
+
513
+ @Delete(':id')
514
+ @Auth(Role.USER)
515
+ @HttpCode(HttpStatus.NO_CONTENT)
516
+ async remove(
517
+ @Param('id') id: string,
518
+ @CurrentUser() user: UserDocument,
519
+ ) {
520
+ return this.orderService.remove(id, user.id)
521
+ }
522
+ }
523
+ ```
524
+
525
+ ### Service Pattern
526
+
527
+ ```typescript
528
+ // features/order/order.service.ts
529
+ @Injectable()
530
+ export class OrderService {
531
+ constructor(
532
+ @InjectModel(Order.name) private orderModel: Model<OrderDocument>,
533
+ private readonly pricingService: OrderPricingService,
534
+ private readonly eventEmitter: EventEmitter2,
535
+ ) {}
536
+
537
+ async findAll(userId: string, pagination: PaginationDto): Promise<PaginatedResponse<Order>> {
538
+ const { skip, limit, sort } = pagination
539
+ const filter = { userId: new Types.ObjectId(userId) }
540
+
541
+ const [docs, totalDocs] = await Promise.all([
542
+ this.orderModel.find(filter).sort(sort).skip(skip).limit(limit).lean().exec(),
543
+ this.orderModel.countDocuments(filter),
544
+ ])
545
+
546
+ return {
547
+ data: docs,
548
+ meta: new PageMeta({ totalDocs, page: Math.floor(skip / limit) + 1, limit }),
549
+ }
550
+ }
551
+
552
+ async findOneOrFail(id: string, userId: string): Promise<Order> {
553
+ const order = await this.orderModel
554
+ .findOne({ _id: id, userId: new Types.ObjectId(userId) })
555
+ .lean()
556
+ .exec()
557
+ if (!order) throw new NotFoundException('Order', id)
558
+ return order
559
+ }
560
+
561
+ async create(userId: string, dto: CreateOrderDto): Promise<Order> {
562
+ const total = await this.pricingService.calculate(dto.items)
563
+ const order = await this.orderModel.create({
564
+ userId: new Types.ObjectId(userId),
565
+ ...dto,
566
+ total,
567
+ })
568
+
569
+ this.eventEmitter.emit('order.created', { orderId: order._id, userId })
570
+ return order.toObject()
571
+ }
572
+
573
+ async remove(id: string, userId: string): Promise<void> {
574
+ const result = await this.orderModel.deleteOne({
575
+ _id: id,
576
+ userId: new Types.ObjectId(userId),
577
+ })
578
+ if (result.deletedCount === 0) throw new NotFoundException('Order', id)
579
+ this.eventEmitter.emit('order.deleted', { orderId: id, userId })
580
+ }
581
+ }
582
+ ```
583
+
584
+ ### DTO Pattern (class-validator + class-transformer)
585
+
586
+ ```typescript
587
+ // features/order/dto/create-order.dto.ts
588
+ import { IsString, IsArray, IsNumber, ValidateNested, Min, ArrayMinSize } from 'class-validator'
589
+ import { Type } from 'class-transformer'
590
+ import { ApiProperty } from '@nestjs/swagger'
591
+
592
+ class OrderItemDto {
593
+ @ApiProperty()
594
+ @IsString()
595
+ productId: string
596
+
597
+ @ApiProperty()
598
+ @IsNumber()
599
+ @Min(1)
600
+ quantity: number
601
+ }
602
+
603
+ export class CreateOrderDto {
604
+ @ApiProperty({ type: [OrderItemDto] })
605
+ @IsArray()
606
+ @ArrayMinSize(1)
607
+ @ValidateNested({ each: true })
608
+ @Type(() => OrderItemDto)
609
+ items: OrderItemDto[]
610
+
611
+ @ApiProperty({ required: false })
612
+ @IsString()
613
+ @IsOptional()
614
+ note?: string
615
+ }
616
+ ```
617
+
618
+ ```typescript
619
+ // features/order/dto/update-order.dto.ts
620
+ import { PartialType } from '@nestjs/swagger'
621
+ import { CreateOrderDto } from './create-order.dto'
622
+
623
+ export class UpdateOrderDto extends PartialType(CreateOrderDto) {}
624
+ ```
625
+
626
+ ### Schema Pattern (Mongoose + NestJS)
627
+
628
+ ```typescript
629
+ // features/order/entities/order.schema.ts
630
+ @Schema({
631
+ timestamps: true,
632
+ toJSON: { virtuals: true },
633
+ toObject: { virtuals: true },
634
+ })
635
+ export class Order {
636
+ @Prop({ type: Types.ObjectId, ref: 'User', required: true, index: true })
637
+ userId: Types.ObjectId
638
+
639
+ @Prop({
640
+ type: [{
641
+ productId: { type: Types.ObjectId, ref: 'Product', required: true },
642
+ quantity: { type: Number, required: true, min: 1 },
643
+ price: { type: Number, required: true },
644
+ }],
645
+ required: true,
646
+ })
647
+ items: Array<{ productId: Types.ObjectId; quantity: number; price: number }>
648
+
649
+ @Prop({ required: true })
650
+ total: number
651
+
652
+ @Prop({ type: String, enum: ['pending', 'confirmed', 'shipped', 'delivered', 'cancelled'], default: 'pending', index: true })
653
+ status: string
654
+
655
+ @Prop()
656
+ note?: string
657
+ }
658
+
659
+ export type OrderDocument = HydratedDocument<Order>
660
+ export const OrderSchema = SchemaFactory.createForClass(Order)
661
+
662
+ // Compound indexes
663
+ OrderSchema.index({ userId: 1, status: 1, createdAt: -1 })
664
+ OrderSchema.index({ status: 1, createdAt: -1 })
665
+ ```
666
+
667
+ ## Event-Driven Communication
668
+
669
+ ### Domain Events
670
+
671
+ Features communicate through events — never import another feature's service directly for side effects.
672
+
673
+ ```typescript
674
+ // shared/events/domain-event.ts
675
+ export enum DomainEvent {
676
+ // Entity lifecycle
677
+ ORDER_CREATED = 'order.created',
678
+ ORDER_DELETED = 'order.deleted',
679
+ USER_DELETED = 'user.deleted',
680
+
681
+ // Cascade cleanup
682
+ CATEGORY_DELETED = 'category.deleted',
683
+
684
+ // Async processing
685
+ VECTOR_SYNC_REQUIRED = 'vector.sync.required',
686
+ NOTIFICATION_SEND = 'notification.send',
687
+ }
688
+ ```
689
+
690
+ ```typescript
691
+ // features/order/order.event.ts
692
+ @Injectable()
693
+ export class OrderEventListener {
694
+ constructor(
695
+ @InjectModel(Order.name) private orderModel: Model<OrderDocument>,
696
+ ) {}
697
+
698
+ @OnEvent(DomainEvent.USER_DELETED, { promisify: true })
699
+ async handleUserDeleted(payload: { userId: string }) {
700
+ await this.orderModel.deleteMany({ userId: new Types.ObjectId(payload.userId) })
701
+ }
702
+ }
703
+ ```
704
+
705
+ ### When to Use Events vs Direct Imports
706
+
707
+ | Scenario | Approach | Why |
708
+ |----------|----------|-----|
709
+ | Feature A reacts to Feature B's action | **Event** | No coupling, Feature B doesn't know about A |
710
+ | Cascading delete (user deleted → delete orders) | **Event** | Each feature owns its cleanup |
711
+ | Need return value from another service | **Direct import** | Events are fire-and-forget |
712
+ | Shared read-only data (lookup, validation) | **Direct import** (export service) | Simple dependency |
713
+ | Async processing (send email, generate image) | **Event → Queue** | Decouple + retry |
714
+
715
+ ## Queue Processing
716
+
717
+ ### Processor Pattern
718
+
719
+ ```typescript
720
+ // features/order/processors/order-fulfillment.processor.ts
721
+ @Processor('order-fulfillment')
722
+ export class OrderFulfillmentProcessor extends BaseProcessor {
723
+ constructor(
724
+ private readonly orderService: OrderService,
725
+ private readonly notificationService: NotificationService,
726
+ ) {
727
+ super()
728
+ }
729
+
730
+ @Process('fulfill')
731
+ async handleFulfillment(job: Job<{ orderId: string }>) {
732
+ const { orderId } = job.data
733
+ this.logger.log(`Processing order fulfillment: ${orderId}`)
734
+
735
+ await job.progress(10)
736
+ const order = await this.orderService.findById(orderId)
737
+
738
+ await job.progress(50)
739
+ await this.orderService.updateStatus(orderId, 'confirmed')
740
+
741
+ await job.progress(90)
742
+ await this.notificationService.send(order.userId, 'Order confirmed')
743
+
744
+ await job.progress(100)
745
+ return { orderId, status: 'confirmed' }
746
+ }
747
+ }
748
+ ```
749
+
750
+ ### Dispatching Jobs
751
+
752
+ ```typescript
753
+ // In service — dispatch async work to queue
754
+ async create(userId: string, dto: CreateOrderDto): Promise<Order> {
755
+ const order = await this.orderModel.create({ userId, ...dto })
756
+
757
+ await this.orderQueueService.addJob('fulfill', { orderId: order._id.toString() }, {
758
+ dedupeKey: `fulfill:${order._id}`,
759
+ dedupeTtl: 60_000,
760
+ })
761
+
762
+ return order.toObject()
763
+ }
764
+ ```
765
+
766
+ ## Shared Infrastructure
767
+
768
+ ### Pagination Decorator + DTO
769
+
770
+ ```typescript
771
+ // shared/decorators/pagination.decorator.ts
772
+ export const GetPagination = createParamDecorator(
773
+ (data: unknown, ctx: ExecutionContext): PaginationDto => {
774
+ const request = ctx.switchToHttp().getRequest()
775
+ const { skip = 0, limit = 20, sort, search } = request.query
776
+
777
+ return {
778
+ skip: Math.max(0, Number(skip)),
779
+ limit: Math.min(100, Math.max(1, Number(limit))),
780
+ sort: parseSort(sort as string), // '-createdAt' → { createdAt: -1 }
781
+ search: parseSearch(search as string), // 'name:john' → { name: /john/i }
782
+ }
783
+ },
784
+ )
785
+
786
+ function parseSort(raw?: string): Record<string, 1 | -1> {
787
+ if (!raw) return { createdAt: -1 }
788
+ const dir = raw.startsWith('-') ? 1 : -1 // '-' = ascending, default = descending
789
+ const field = raw.replace(/^[+-]/, '')
790
+ return { [field]: dir }
791
+ }
792
+ ```
793
+
794
+ ```typescript
795
+ // shared/dto/pagination.dto.ts
796
+ export class PageMeta {
797
+ totalDocs: number
798
+ page: number
799
+ limit: number
800
+ totalPages: number
801
+ hasNextPage: boolean
802
+ hasPrevPage: boolean
803
+
804
+ constructor(opts: { totalDocs: number; page: number; limit: number }) {
805
+ this.totalDocs = opts.totalDocs
806
+ this.page = opts.page
807
+ this.limit = opts.limit
808
+ this.totalPages = Math.ceil(opts.totalDocs / opts.limit)
809
+ this.hasNextPage = opts.page < this.totalPages
810
+ this.hasPrevPage = opts.page > 1
811
+ }
812
+ }
813
+
814
+ export class PaginatedResponse<T> {
815
+ data: T[]
816
+ meta: PageMeta
817
+ }
818
+ ```
819
+
820
+ ### Shared Schema Mixins
821
+
822
+ ```typescript
823
+ // shared/schema/priority.schema.ts
824
+ export const PriorityMixin = {
825
+ priority: { type: Number, default: 0, index: true },
826
+ sortOrder: { type: Number, default: 0 },
827
+ }
828
+
829
+ // Usage in any feature schema:
830
+ @Schema({ timestamps: true })
831
+ export class Category {
832
+ @Prop({ required: true })
833
+ name: string
834
+
835
+ @Prop({ default: 0, index: true })
836
+ priority: number
837
+ }
838
+ ```
839
+
840
+ ## Bootstrap (main.ts)
841
+
842
+ ```typescript
843
+ // main.ts
844
+ async function bootstrap() {
845
+ // Validate environment first
846
+ const env = envSchema.safeParse(process.env)
847
+ if (!env.success) {
848
+ console.error('Invalid env vars:', env.error.format())
849
+ process.exit(1)
850
+ }
851
+
852
+ const app = await NestFactory.create(AppModule)
853
+
854
+ // Global prefix
855
+ app.setGlobalPrefix('api/v1', { exclude: ['health'] })
856
+
857
+ // Security
858
+ app.use(helmet(helmetConfig))
859
+ app.enableCors(corsConfig)
860
+
861
+ // Global pipes
862
+ app.useGlobalPipes(new ValidationPipe({
863
+ transform: true,
864
+ whitelist: true,
865
+ forbidNonWhitelisted: true,
866
+ transformOptions: { enableImplicitConversion: true },
867
+ }))
868
+
869
+ // Global filters
870
+ app.useGlobalFilters(new HttpExceptionFilter())
871
+
872
+ // Swagger
873
+ if (process.env.SWAGGER_ENABLED === 'true') {
874
+ setupSwagger(app)
875
+ }
876
+
877
+ // Graceful shutdown
878
+ app.enableShutdownHooks()
879
+
880
+ await app.listen(process.env.PORT || 3000)
881
+ }
882
+
883
+ bootstrap()
884
+ ```
885
+
886
+ ## Root Module
887
+
888
+ ```typescript
889
+ // app.module.ts
890
+ @Module({
891
+ imports: [
892
+ // Core infrastructure (order matters for dependencies)
893
+ ConfigModule.forRoot({ isGlobal: true }),
894
+ DatabaseModule,
895
+ RedisModule,
896
+ LoggerModule,
897
+ HealthModule,
898
+ QueueModule,
899
+ SchedulerModule,
900
+ EventEmitterModule.forRoot({ wildcard: true }),
901
+ CacheModule.registerAsync({
902
+ isGlobal: true,
903
+ inject: [ConfigService],
904
+ useFactory: (config: ConfigService) => ({
905
+ store: redisStore,
906
+ host: config.get('REDIS_HOST'),
907
+ port: config.get('REDIS_PORT'),
908
+ ttl: 60,
909
+ }),
910
+ }),
911
+
912
+ // Feature modules
913
+ AuthModule,
914
+ UserModule,
915
+ OrderModule,
916
+ NotificationModule,
917
+ MediaModule,
918
+ // ... other features
919
+ ],
920
+ controllers: [AppController],
921
+ providers: [AppService],
922
+ })
923
+ export class AppModule {}
924
+ ```
925
+
926
+ ## Common Anti-Patterns
927
+
928
+ ### 1. Organizing by Technical Layer
929
+
930
+ ```
931
+ # BAD — "technical layer" structure
932
+ src/
933
+ ├── controllers/
934
+ │ ├── user.controller.ts
935
+ │ ├── order.controller.ts
936
+ │ └── product.controller.ts
937
+ ├── services/
938
+ │ ├── user.service.ts
939
+ │ ├── order.service.ts
940
+ │ └── product.service.ts
941
+ ├── entities/
942
+ │ ├── user.schema.ts
943
+ │ └── order.schema.ts
944
+
945
+ # GOOD — "feature-based" structure
946
+ src/features/
947
+ ├── user/
948
+ │ ├── user.controller.ts
949
+ │ ├── user.service.ts
950
+ │ └── entities/user.schema.ts
951
+ ├── order/
952
+ │ ├── order.controller.ts
953
+ │ ├── order.service.ts
954
+ │ └── entities/order.schema.ts
955
+ ```
956
+
957
+ ### 2. God Module (Everything in AppModule)
958
+
959
+ ```typescript
960
+ // BAD — all providers in root module
961
+ @Module({
962
+ providers: [UserService, OrderService, ProductService, EmailService, ...50 more],
963
+ })
964
+ export class AppModule {}
965
+
966
+ // GOOD — each feature is its own module
967
+ @Module({
968
+ imports: [UserModule, OrderModule, ProductModule],
969
+ })
970
+ export class AppModule {}
971
+ ```
972
+
973
+ ### 3. Cross-Feature Direct Service Imports for Side Effects
974
+
975
+ ```typescript
976
+ // BAD — tight coupling: OrderService directly calls NotificationService
977
+ @Injectable()
978
+ export class OrderService {
979
+ constructor(private notificationService: NotificationService) {}
980
+
981
+ async create(dto) {
982
+ const order = await this.orderModel.create(dto)
983
+ await this.notificationService.sendOrderConfirmation(order) // tight coupling
984
+ }
985
+ }
986
+
987
+ // GOOD — emit event, let NotificationModule listen
988
+ @Injectable()
989
+ export class OrderService {
990
+ constructor(private eventEmitter: EventEmitter2) {}
991
+
992
+ async create(dto) {
993
+ const order = await this.orderModel.create(dto)
994
+ this.eventEmitter.emit('order.created', { orderId: order._id }) // decoupled
995
+ }
996
+ }
997
+ ```
998
+
999
+ ### 4. No Global Exception Filter
1000
+
1001
+ ```typescript
1002
+ // BAD — try/catch in every controller method
1003
+ @Get(':id')
1004
+ async findOne(@Param('id') id: string) {
1005
+ try {
1006
+ return await this.service.findOne(id)
1007
+ } catch (error) {
1008
+ throw new HttpException(error.message, 500)
1009
+ }
1010
+ }
1011
+
1012
+ // GOOD — throw domain exceptions, global filter handles formatting
1013
+ @Get(':id')
1014
+ async findOne(@Param('id') id: string) {
1015
+ return this.service.findOneOrFail(id) // throws NotFoundException if missing
1016
+ }
1017
+ ```
1018
+
1019
+ ### 5. Business Logic in Controllers
1020
+
1021
+ ```typescript
1022
+ // BAD — controller does business logic
1023
+ @Post()
1024
+ async create(@Body() dto: CreateOrderDto) {
1025
+ const price = dto.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
1026
+ const tax = price * 0.1
1027
+ const order = await this.orderModel.create({ ...dto, total: price + tax })
1028
+ await this.mailerService.send(...)
1029
+ return order
1030
+ }
1031
+
1032
+ // GOOD — controller delegates to service
1033
+ @Post()
1034
+ async create(@Body() dto: CreateOrderDto) {
1035
+ return this.orderService.create(dto)
1036
+ }
1037
+ ```
1038
+
1039
+ ### 6. Missing Graceful Shutdown
1040
+
1041
+ ```typescript
1042
+ // BAD — active queue jobs lost on deploy, connections leaked
1043
+
1044
+ // GOOD — clean shutdown
1045
+ app.enableShutdownHooks()
1046
+
1047
+ @Injectable()
1048
+ export class AppCleanupService implements OnApplicationShutdown {
1049
+ constructor(private queueService: BaseQueueService) {}
1050
+
1051
+ async onApplicationShutdown(signal: string) {
1052
+ logger.log(`Shutting down on ${signal}`)
1053
+ await this.queueService.gracefulShutdown(30_000)
1054
+ await mongoose.connection.close()
1055
+ }
1056
+ }
1057
+ ```
1058
+
1059
+ ## Module Dependency Rules
1060
+
1061
+ 1. **Core modules** are `@Global()` — imported once in `AppModule`, available everywhere
1062
+ 2. **Feature modules** import only what they need — use `imports: [OtherModule]` and consume exported services
1063
+ 3. **Shared utilities** are standalone — imported directly in features that need them
1064
+ 4. **Circular dependencies** resolved with `forwardRef(() => ModuleA)` — keep these rare
1065
+ 5. **Feature-to-feature communication** prefers events over direct imports
1066
+ 6. **Never import a feature module just for a type** — move shared types to `shared/interfaces/`
1067
+
1068
+ ## Quick Reference
1069
+
1070
+ | Task | Pattern |
1071
+ |------|---------|
1072
+ | New feature | Create `features/<name>/` with module, controller, service, dto/, entities/ |
1073
+ | Cross-feature side effect | `EventEmitter2` + `@OnEvent()` listener |
1074
+ | Async heavy work | Bull queue + processor |
1075
+ | Input validation | class-validator DTOs + global `ValidationPipe` |
1076
+ | Auth on route | `@Auth(Role.USER)` + `@CurrentUser()` |
1077
+ | Pagination | `@GetPagination()` decorator + `PaginatedResponse<T>` |
1078
+ | Error response | Throw `AppException` subclass, global filter formats it |
1079
+ | Cron job | `@Cron()` in `SchedulerService` |
1080
+ | Health check | Add indicator in `HealthController.check()` |
1081
+ | Config value | `ConfigService.get('KEY')` — validated at startup |
1082
+
1083
+ ## Further Reading
1084
+
1085
+ For detailed reference on specific topics, see:
1086
+ - `references/advanced-patterns.md` — Middleware chains, custom decorators, interceptors, versioning, testing strategies, Docker setup, CI/CD patterns