@@ -452,7 +452,7 @@ const { data } = useQuery({
452452- Sensitive data (passwords, tokens, API keys)
453453- Real-time data (notifications, live counts)
454454- Temporary search/filter results
455- Configured globally in `src/app/providers.tsx` with `PersistQueryClientProvider`.
455+ Configured globally in `src/app/providers.tsx` with `PersistQueryClientProvider`.
456456
457457**staleTime vs gcTime:**
458458
@@ -785,6 +785,164 @@ export const usedFunction = () => {}; // Keep
785785// Ignore "unlisted dependencies" warnings
786786```
787787
788+ ## Background Jobs & BullMQ Worker Pattern - MANDATORY
789+
790+ **Use factory functions for workers to avoid module-level side effects.**
791+
792+ ### The Problem: Accidental Worker Initialization
793+
794+ When workers are created at module level, importing the module immediately starts workers:
795+
796+ ```typescript
797+ // ❌ BAD - Worker starts on import (side effect)
798+ export const buildWorker = createWorker(...);
799+ const events = createQueueEvents(...);
800+ events.on('completed', ...);
801+ console.log('Worker initialized'); // Runs immediately!
802+ ```
803+
804+ **Consequence:** Importing this module in API routes accidentally starts workers in the Next.js container.
805+
806+ ### The Solution: Factory Functions
807+
808+ Use factory functions that only initialize workers when explicitly called:
809+
810+ ```typescript
811+ // ✅ GOOD - No side effects, safe to import anywhere
812+ export function createBuildWorker() {
813+ const worker = createWorker(...);
814+ const events = createQueueEvents(...);
815+ events.on('completed', ...);
816+ console.log('Worker initialized'); // Only runs when called
817+ return worker;
818+ }
819+ ```
820+
821+ ### Implementation Pattern
822+
823+ **Worker Module** (`src/lib/queue/workers/build.ts`):
824+
825+ ```typescript
826+ /**
827+ * Build Worker - Factory function (no side effects)
828+ */
829+
830+ async function processBuildJob(job: { data: BuildJobData }): Promise<BuildJobResult> {
831+ // Job processing logic
832+ }
833+
834+ /**
835+ * Create and initialize build worker
836+ * Factory function - NO side effects until called
837+ */
838+ export function createBuildWorker() {
839+ const worker = createWorker<BuildJobData>(QUEUE_NAMES.BUILD, processBuildJob, {
840+ concurrency: 1,
841+ });
842+
843+ const events = createQueueEvents(QUEUE_NAMES.BUILD);
844+
845+ events.on('completed', ({ jobId, returnvalue }) => {
846+ console.log(`[WORKER] ✅ Job ${jobId} completed`);
847+ });
848+
849+ events.on('failed', ({ jobId, failedReason }) => {
850+ console.error(`[WORKER] ❌ Job ${jobId} failed:`, failedReason);
851+ });
852+
853+ console.log('[WORKER] 🚀 Build Worker Initialized');
854+
855+ return worker;
856+ }
857+ ```
858+
859+ **Worker Process** (`src/worker.ts`):
860+
861+ ```typescript
862+ import { createBuildWorker } from '@/lib/queue/workers/build';
863+ import { createPreviewWorker } from '@/lib/queue/workers/previews';
864+
865+ async function main() {
866+ console.log('[WORKER] 🚀 Starting BullMQ worker process...\n');
867+
868+ // Initialize workers (explicit - no side effects on import)
869+ const previewWorker = createPreviewWorker();
870+ const buildWorker = createBuildWorker();
871+
872+ // Graceful shutdown handlers
873+ process.on('SIGTERM', async () => {
874+ await gracefulShutdown([previewWorker, buildWorker], [previewQueue, buildQueue]);
875+ process.exit(0);
876+ });
877+ }
878+
879+ main();
880+ ```
881+
882+ **Barrel Export** (`src/lib/queue/index.ts`):
883+
884+ ```typescript
885+ /**
886+ * BullMQ Queue Module
887+ * Safe to import anywhere - worker factories have NO side effects
888+ */
889+
890+ // Queues (for enqueueing jobs in API routes)
891+ export { buildQueue, type BuildJobData, type BuildJobResult } from './queues/build';
892+ export { previewQueue, schedulePreviewCleanup } from './queues/previews';
893+
894+ // Worker factories (for worker process only, but safe to import anywhere)
895+ export { createBuildWorker } from './workers/build';
896+ export { createPreviewWorker } from './workers/previews';
897+
898+ // Helpers
899+ export async function enqueueBuild(data: BuildJobData): Promise<void> {
900+ const { buildQueue } = await import('./queues/build');
901+ await buildQueue.add('process-build', data);
902+ }
903+ ```
904+
905+ ### Usage Patterns
906+
907+ **In API Routes** (enqueue jobs):
908+
909+ ```typescript
910+ import { buildQueue, enqueueBuild } from '@/lib/queue';
911+
912+ // Enqueue a build job
913+ await enqueueBuild({
914+ buildJobId: 'abc123',
915+ projectId: 'proj-1',
916+ sessionId: 'sess-1',
917+ ticketsPath: 'tickets/2024-01-01.json',
918+ });
919+ ```
920+
921+ **In Worker Process** (process jobs):
922+
923+ ```typescript
924+ import { createBuildWorker, createPreviewWorker } from '@/lib/queue';
925+
926+ // Explicit initialization - only in worker process
927+ const buildWorker = createBuildWorker();
928+ const previewWorker = createPreviewWorker();
929+ ```
930+
931+ ### Benefits
932+
933+ ✅ **No accidental worker initialization** - Workers only run when explicitly called
934+ ✅ **Safe barrel exports** - Can export factory functions without side effects
935+ ✅ **Clear initialization** - Worker lifecycle is visible and controllable
936+ ✅ **Better testing** - Can test worker logic without starting actual workers
937+ ✅ **Explicit control** - Worker lifecycle is visible in `src/worker.ts`
938+
939+ ### Rules
940+
941+ - ❌ **NEVER** create workers at module level with `export const worker = createWorker(...)`
942+ - ✅ **ALWAYS** use factory functions: `export function createWorker() { return createWorker(...); }`
943+ - ✅ **ALWAYS** call worker factories explicitly in `src/worker.ts`
944+ - ✅ **NEVER** call worker factories in API routes (only import queues)
945+
788946## TypeScript and Type Safety Guidelines
789947
790948- Never use the `any` type - it defeats TypeScript's type checking
@@ -816,7 +974,6 @@ export const usedFunction = () => {}; // Keep
8169742. **Domain Extension Types** - Only define in `lib/types/` when extending base types AND actively used
8179753. **Infrastructure Types** - API utilities, errors, and configurations in `lib/api/`
818976
819-
820977### Type Naming Conventions
821978
822979- Base types: `User`, `UserSubscription`, `Task` (match schema exports)
@@ -826,7 +983,6 @@ export const usedFunction = () => {}; // Keep
826983- Input types: Infer from router input `RouterInput['tasks']['create']`
827984- Statistics: `UserStats`, `BillingStats` (computed aggregations)
828985
829-
830986## Centralized Type Organization Rules - MANDATORY
831987
832988**NEVER define types inside hooks, components, or utility functions. ALL types must be centralized.**
@@ -872,7 +1028,6 @@ export interface AsyncOperationOptions {
8721028 onSuccess?: () => void;
8731029 onError?: (error: Error) => void;
8741030}
875-
8761031```
8771032
8781033### ❌ WRONG - Manual type definitions
@@ -953,7 +1108,6 @@ import { tasks } from '@/lib/db/schema'; // OK in database queries
9531108
9541109**Sitemap** only includes legal pages in `app/sitemap.ts`.
9551110
956-
9571111## GitHub Actions Workflow Notifications - MANDATORY
9581112
9591113**When creating new GitHub Actions workflows, ALWAYS add Slack notifications for success and failure outcomes.**
0 commit comments