@igniter-js/jobs 0.1.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.
package/AGENTS.md ADDED
@@ -0,0 +1,557 @@
1
+ # @igniter-js/jobs - Agent Manual
2
+
3
+ > **Last Updated:** 2025-01-01
4
+ > **Version:** 0.1.0
5
+
6
+ ## 1. Package Overview
7
+
8
+ ### Purpose
9
+
10
+ `@igniter-js/jobs` provides type-safe background job processing for TypeScript applications, powered by BullMQ and Redis. It follows the Igniter Builder API pattern for maximum type safety and developer experience.
11
+
12
+ ### Core Concepts
13
+
14
+ | Concept | Description |
15
+ |---------|-------------|
16
+ | **Queue** | Named container for jobs (e.g., `email`, `notifications`) |
17
+ | **Job** | Unit of work with typed payload and handler |
18
+ | **Cron Job** | Scheduled job with pattern or interval |
19
+ | **Worker** | Process that executes jobs from queues |
20
+ | **Scope** | Multi-tenant context (e.g., organization, workspace) |
21
+ | **Actor** | Entity that triggered the job (e.g., user, system) |
22
+ | **Adapter** | Implementation for queue backend (BullMQ, Memory) |
23
+
24
+ ### Package Exports
25
+
26
+ ```typescript
27
+ // Main entry point
28
+ import { IgniterJobs, IgniterQueue, IgniterJobsError } from '@igniter-js/jobs'
29
+
30
+ // Adapters
31
+ import { BullMQAdapter, MemoryAdapter } from '@igniter-js/jobs/adapters'
32
+ ```
33
+
34
+ ---
35
+
36
+ ## 2. Architecture
37
+
38
+ ### File Structure
39
+
40
+ ```
41
+ packages/jobs/
42
+ ├── src/
43
+ │ ├── index.ts # Main exports
44
+ │ ├── types/
45
+ │ │ ├── index.ts # Type exports
46
+ │ │ ├── scope.ts # Scope/Actor types
47
+ │ │ ├── job.ts # Job definition types
48
+ │ │ ├── queue.ts # Queue config types
49
+ │ │ ├── events.ts # Event types
50
+ │ │ ├── adapter.ts # Adapter interface
51
+ │ │ ├── config.ts # Builder state types
52
+ │ │ └── worker.ts # Worker types
53
+ │ ├── errors/
54
+ │ │ ├── index.ts # Error exports
55
+ │ │ └── igniter-jobs.error.ts # Error class and codes
56
+ │ ├── builders/
57
+ │ │ ├── index.ts # Builder exports
58
+ │ │ ├── igniter-queue.builder.ts # Queue builder
59
+ │ │ ├── igniter-jobs.builder.ts # Jobs builder
60
+ │ │ └── igniter-worker.builder.ts # Worker builder
61
+ │ ├── core/
62
+ │ │ ├── index.ts # Core exports
63
+ │ │ └── igniter-jobs.ts # Runtime class
64
+ │ └── adapters/
65
+ │ ├── index.ts # Adapter exports
66
+ │ ├── bullmq.adapter.ts # BullMQ implementation
67
+ │ └── memory.adapter.ts # Memory implementation
68
+ ├── tests/
69
+ │ ├── igniter-queue.builder.test.ts
70
+ │ ├── igniter-jobs.builder.test.ts
71
+ │ ├── igniter-jobs.runtime.test.ts
72
+ │ └── memory.adapter.test.ts
73
+ ├── package.json
74
+ ├── tsconfig.json
75
+ ├── tsup.config.ts
76
+ ├── vitest.config.ts
77
+ ├── README.md
78
+ └── AGENTS.md # This file
79
+ ```
80
+
81
+ ### Builder Pattern Flow
82
+
83
+ ```
84
+ 1. IgniterQueue.create('name')
85
+ └─► addJob() / addCron()
86
+ └─► build() → IgniterQueueBuiltConfig
87
+
88
+ 2. IgniterJobs.create()
89
+ └─► withAdapter()
90
+ └─► addQueue()
91
+ └─► addScope() / addActor()
92
+ └─► withDefaults()
93
+ └─► build() → IgniterJobsRuntime (with Proxy)
94
+
95
+ 3. jobs.queueName.jobName.dispatch(data)
96
+ └─► Proxy intercepts access
97
+ └─► Returns typed dispatch function
98
+ ```
99
+
100
+ ### Proxy-Based API
101
+
102
+ The runtime uses JavaScript Proxy to provide type-safe access:
103
+
104
+ ```typescript
105
+ // How it works internally:
106
+ jobs.email.sendWelcome.dispatch(data)
107
+ │ │ │
108
+ │ │ └── Job proxy with dispatch/schedule methods
109
+ │ └── Queue proxy returning job proxies
110
+ └── Root proxy returning queue proxies
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 3. Key Components
116
+
117
+ ### 3.1 IgniterQueue Builder
118
+
119
+ **Location:** `src/builders/igniter-queue.builder.ts`
120
+
121
+ **Purpose:** Define queue configuration with type-safe job definitions.
122
+
123
+ **Key Methods:**
124
+ - `create(name, options?)` - Initialize builder
125
+ - `addJob(name, config)` - Add job with schema and handler
126
+ - `addCron(name, config)` - Add cron job
127
+ - `build()` - Return frozen config
128
+
129
+ **Type Flow:**
130
+ ```typescript
131
+ IgniterQueue.create('email')
132
+ .addJob('send', { schema, handler })
133
+ // Jobs type accumulates: { send: {...} }
134
+ .build()
135
+ // Returns: IgniterQueueBuiltConfig<'email', Jobs>
136
+ ```
137
+
138
+ ### 3.2 IgniterJobs Builder
139
+
140
+ **Location:** `src/builders/igniter-jobs.builder.ts`
141
+
142
+ **Purpose:** Create jobs runtime with adapter, context, and queues.
143
+
144
+ **Key Methods:**
145
+ - `create<Context>()` - Initialize with context type
146
+ - `withAdapter(adapter)` - Set queue adapter (required)
147
+ - `withContext(factory)` - Set context factory for handlers
148
+ - `addQueue(queue)` - Add queue configuration
149
+ - `addScope(name, config)` - Define scope type
150
+ - `addActor(name, config)` - Define actor type
151
+ - `withTelemetry(adapter)` - Enable tracing
152
+ - `withDefaults(options)` - Default job options
153
+ - `build()` - Return runtime instance
154
+
155
+ ### 3.3 IgniterJobs Runtime
156
+
157
+ **Location:** `src/core/igniter-jobs.ts`
158
+
159
+ **Purpose:** Main runtime class with proxy-based access.
160
+
161
+ **Key Properties:**
162
+ - `[queueName]` - Queue proxy (via Proxy)
163
+ - `job` - Job management methods
164
+ - `queue` - Queue management methods
165
+ - `subscribe` - Event subscription
166
+ - `search` - Search methods
167
+ - `worker` - Worker builder access
168
+ - `shutdown()` - Graceful shutdown
169
+
170
+ ### 3.4 Adapters
171
+
172
+ **BullMQ Adapter:** `src/adapters/bullmq.adapter.ts`
173
+ - Production-ready with Redis
174
+ - Supports all BullMQ features
175
+ - Lazy loads BullMQ module
176
+
177
+ **Memory Adapter:** `src/adapters/memory.adapter.ts`
178
+ - For testing/development
179
+ - No external dependencies
180
+ - Simulates job processing
181
+
182
+ ---
183
+
184
+ ## 4. Type System
185
+
186
+ ### Core Type Hierarchy
187
+
188
+ ```typescript
189
+ // Job Definition (queue level)
190
+ interface IgniterJobDefinition<TData, TResult, TContext> {
191
+ schema: ZodSchema<TData>
192
+ handler: IgniterJobHandler<TData, TResult, TContext>
193
+ options?: IgniterJobOptions
194
+ }
195
+
196
+ // Handler Context (passed to handler)
197
+ interface IgniterJobHandlerContext<TData, TContext> {
198
+ data: TData
199
+ context: TContext
200
+ job: { id: string; name: string; queue: string; attempt: number }
201
+ log: (level, message) => Promise<void>
202
+ updateProgress: (progress) => Promise<void>
203
+ scope?: IgniterJobScopeEntry
204
+ actor?: IgniterJobActorEntry
205
+ }
206
+
207
+ // Dispatch Options
208
+ interface DispatchOptions {
209
+ delay?: number
210
+ priority?: number
211
+ attempts?: number
212
+ backoff?: IgniterJobBackoff
213
+ jobId?: string
214
+ scope?: { type: string; id: string }
215
+ actor?: { type: string; id: string }
216
+ }
217
+ ```
218
+
219
+ ### Type Inference Chain
220
+
221
+ ```typescript
222
+ // 1. Define schema
223
+ const schema = z.object({ email: z.string() })
224
+ // Type: z.ZodObject<{ email: z.ZodString }>
225
+
226
+ // 2. Schema infers handler data type
227
+ handler: async ({ data }) => { ... }
228
+ // data type: { email: string }
229
+
230
+ // 3. Queue builder accumulates job types
231
+ .addJob('sendWelcome', { schema, handler })
232
+ // Jobs type: { sendWelcome: { data: { email: string } } }
233
+
234
+ // 4. Runtime proxy provides typed dispatch
235
+ jobs.email.sendWelcome.dispatch({ email: 'x' })
236
+ // Argument type: { email: string }
237
+ ```
238
+
239
+ ---
240
+
241
+ ## 5. Error Handling
242
+
243
+ ### Error Codes
244
+
245
+ **Configuration Errors:**
246
+ - `JOBS_MISSING_ADAPTER` - No adapter provided
247
+ - `JOBS_INVALID_QUEUE_NAME` - Invalid queue name
248
+ - `JOBS_INVALID_JOB_NAME` - Invalid job name
249
+ - `JOBS_DUPLICATE_QUEUE` - Queue already registered
250
+ - `JOBS_DUPLICATE_JOB` - Job already registered
251
+
252
+ **Queue Errors:**
253
+ - `JOBS_QUEUE_NOT_FOUND` - Queue doesn't exist
254
+ - `JOBS_QUEUE_PAUSED` - Queue is paused
255
+ - `JOBS_QUEUE_FAILED_TO_PAUSE` - Pause operation failed
256
+
257
+ **Job Errors:**
258
+ - `JOBS_JOB_NOT_FOUND` - Job doesn't exist
259
+ - `JOBS_JOB_VALIDATION_FAILED` - Payload validation failed
260
+ - `JOBS_JOB_HANDLER_FAILED` - Handler threw error
261
+ - `JOBS_JOB_TIMEOUT` - Job exceeded timeout
262
+
263
+ **Dispatch Errors:**
264
+ - `JOBS_DISPATCH_FAILED` - Dispatch operation failed
265
+ - `JOBS_SCHEDULE_FAILED` - Schedule operation failed
266
+ - `JOBS_INVALID_DISPATCH_PAYLOAD` - Invalid payload
267
+
268
+ ### Error Class
269
+
270
+ ```typescript
271
+ throw new IgniterJobsError({
272
+ code: 'JOBS_JOB_NOT_FOUND',
273
+ message: `Job "${jobId}" not found in queue "${queue}"`,
274
+ statusCode: 404,
275
+ details: { jobId, queue },
276
+ })
277
+ ```
278
+
279
+ ---
280
+
281
+ ## 6. Development Guidelines
282
+
283
+ ### Adding New Features
284
+
285
+ 1. **Add Types First**
286
+ - Define in appropriate `src/types/*.ts` file
287
+ - Export from `src/types/index.ts`
288
+ - Export from `src/index.ts` if public
289
+
290
+ 2. **Update Builders**
291
+ - Add method to builder class
292
+ - Update state type if needed
293
+ - Maintain fluent return type
294
+
295
+ 3. **Update Runtime**
296
+ - Implement in `src/core/igniter-jobs.ts`
297
+ - Add proxy handling if needed
298
+
299
+ 4. **Update Adapter Interface**
300
+ - Add method to `IgniterJobsAdapter`
301
+ - Implement in BullMQ adapter
302
+ - Implement in Memory adapter
303
+
304
+ 5. **Add Tests**
305
+ - Unit test for builder method
306
+ - Integration test for runtime
307
+ - Adapter test for implementation
308
+
309
+ ### Testing Strategy
310
+
311
+ ```bash
312
+ # Run all tests
313
+ npm test
314
+
315
+ # Watch mode
316
+ npm run test:watch
317
+
318
+ # Coverage
319
+ npm run test:coverage
320
+ ```
321
+
322
+ **Test Files:**
323
+ - `tests/igniter-queue.builder.test.ts` - Queue builder
324
+ - `tests/igniter-jobs.builder.test.ts` - Jobs builder
325
+ - `tests/igniter-jobs.runtime.test.ts` - Integration
326
+ - `tests/memory.adapter.test.ts` - Memory adapter
327
+
328
+ ### Code Style
329
+
330
+ - Use JSDoc for all public APIs
331
+ - Follow existing patterns exactly
332
+ - No `any` types in public API
333
+ - Export types explicitly
334
+ - Use `readonly` where appropriate
335
+
336
+ ---
337
+
338
+ ## 7. Adapter Implementation Guide
339
+
340
+ ### Required Methods
341
+
342
+ When implementing a new adapter, implement ALL methods from `IgniterJobsAdapter`:
343
+
344
+ ```typescript
345
+ interface IgniterJobsAdapter {
346
+ // Connection
347
+ readonly client: unknown
348
+
349
+ // Job Operations
350
+ dispatch(params: AdapterDispatchParams): Promise<string>
351
+ schedule(params: AdapterScheduleParams): Promise<string>
352
+ getJob(queue: string, jobId: string): Promise<IgniterJobInfo | null>
353
+ getJobState(queue: string, jobId: string): Promise<IgniterJobStatus | null>
354
+ getJobProgress(queue: string, jobId: string): Promise<number>
355
+ getJobLogs(queue: string, jobId: string): Promise<IgniterJobLog[]>
356
+ retryJob(queue: string, jobId: string): Promise<void>
357
+ removeJob(queue: string, jobId: string): Promise<void>
358
+ promoteJob(queue: string, jobId: string): Promise<void>
359
+ moveJob(queue: string, jobId: string, state: string, reason?: string): Promise<void>
360
+ retryJobs(queue: string, jobIds: string[]): Promise<void>
361
+ removeJobs(queue: string, jobIds: string[]): Promise<void>
362
+
363
+ // Queue Operations
364
+ getQueue(queue: string): Promise<IgniterQueueInfo>
365
+ pauseQueue(queue: string): Promise<void>
366
+ resumeQueue(queue: string): Promise<void>
367
+ drainQueue(queue: string): Promise<number>
368
+ cleanQueue(queue: string, options: IgniterQueueCleanOptions): Promise<number>
369
+ obliterateQueue(queue: string, options?: { force?: boolean }): Promise<void>
370
+ retryAllFailed(queue: string): Promise<number>
371
+ getJobCounts(queue: string): Promise<IgniterJobCounts>
372
+ listJobs(queue: string, options?: {...}): Promise<IgniterJobSearchResult[]>
373
+ pauseJobType(queue: string, jobName: string): Promise<void>
374
+ resumeJobType(queue: string, jobName: string): Promise<void>
375
+
376
+ // Events
377
+ subscribe(pattern: string, handler: IgniterJobEventHandler): Promise<IgniterJobUnsubscribeFn>
378
+
379
+ // Workers
380
+ createWorker(config: AdapterWorkerConfig, handler: AdapterJobHandler): Promise<AdapterWorkerHandle>
381
+
382
+ // Search
383
+ searchJobs(filter: {...}): Promise<IgniterJobSearchResult[]>
384
+ searchQueues(filter: {...}): Promise<IgniterQueueInfo[]>
385
+
386
+ // Lifecycle
387
+ shutdown(): Promise<void>
388
+ }
389
+ ```
390
+
391
+ ---
392
+
393
+ ## 8. Common Patterns
394
+
395
+ ### Multi-tenant Job Dispatch
396
+
397
+ ```typescript
398
+ const jobs = IgniterJobs.create()
399
+ .withAdapter(adapter)
400
+ .addQueue(emailQueue)
401
+ .addScope('organization', {
402
+ resolver: (id) => db.org.findUnique({ where: { id } }),
403
+ })
404
+ .addActor('user', {
405
+ resolver: (id) => db.user.findUnique({ where: { id } }),
406
+ })
407
+ .build()
408
+
409
+ // Dispatch with scope/actor
410
+ await jobs.email.sendWelcome.dispatch(
411
+ { email: 'test@example.com' },
412
+ {
413
+ scope: { type: 'organization', id: 'org-123' },
414
+ actor: { type: 'user', id: 'user-456' },
415
+ }
416
+ )
417
+ ```
418
+
419
+ ### Job With Progress Updates
420
+
421
+ ```typescript
422
+ const queue = IgniterQueue.create('exports')
423
+ .addJob('generateReport', {
424
+ schema: z.object({ reportId: z.string() }),
425
+ handler: async ({ data, updateProgress, log }) => {
426
+ await updateProgress(10)
427
+ await log('info', 'Starting report generation')
428
+
429
+ // ... process
430
+
431
+ await updateProgress(50)
432
+ await log('info', 'Halfway done')
433
+
434
+ // ... finish
435
+
436
+ await updateProgress(100)
437
+ return { success: true }
438
+ },
439
+ })
440
+ .build()
441
+ ```
442
+
443
+ ### Scheduled Jobs
444
+
445
+ ```typescript
446
+ // Schedule at specific time
447
+ await jobs.email.sendWelcome.schedule({
448
+ data: { email: 'test@example.com' },
449
+ at: new Date('2025-12-25T00:00:00Z'),
450
+ })
451
+
452
+ // Schedule with delay
453
+ await jobs.email.sendWelcome.schedule({
454
+ data: { email: 'test@example.com' },
455
+ delay: 60000, // 1 minute
456
+ })
457
+ ```
458
+
459
+ ### Worker with Rate Limiting
460
+
461
+ ```typescript
462
+ const worker = await jobs.worker
463
+ .forQueues(['email'])
464
+ .withConcurrency(10)
465
+ .withLimiter({
466
+ max: 100, // max jobs
467
+ duration: 60000, // per minute
468
+ })
469
+ .withLockDuration(30000)
470
+ .onIdle(() => console.log('No jobs to process'))
471
+ .start()
472
+ ```
473
+
474
+ ---
475
+
476
+ ## 9. Version History
477
+
478
+ ### 0.1.0 (Initial Release)
479
+
480
+ - ✅ IgniterQueue builder with addJob/addCron
481
+ - ✅ IgniterJobs builder with full configuration
482
+ - ✅ IgniterWorker builder with fluent API
483
+ - ✅ Proxy-based runtime for type-safe access
484
+ - ✅ BullMQ adapter with full feature support
485
+ - ✅ Memory adapter for testing
486
+ - ✅ Scope and actor support
487
+ - ✅ Event subscription system
488
+ - ✅ Job and queue search
489
+ - ✅ Error codes and classes
490
+
491
+ ---
492
+
493
+ ## 10. Troubleshooting
494
+
495
+ ### Common Issues
496
+
497
+ **"JOBS_MISSING_ADAPTER"**
498
+ - Ensure `withAdapter()` is called before `build()`
499
+ - Check adapter is properly instantiated
500
+
501
+ **"JOBS_QUEUE_NOT_FOUND"**
502
+ - Queue must be registered with `addQueue()`
503
+ - Check queue name spelling
504
+
505
+ **"JOBS_JOB_VALIDATION_FAILED"**
506
+ - Payload doesn't match Zod schema
507
+ - Check `details.issues` for validation errors
508
+
509
+ **TypeScript errors on dispatch**
510
+ - Ensure queue is added before build
511
+ - Check job name exists in queue
512
+ - Verify payload matches schema
513
+
514
+ ### Debug Tips
515
+
516
+ ```typescript
517
+ // Enable BullMQ debug logging
518
+ process.env.DEBUG = 'bullmq:*'
519
+
520
+ // Check job state
521
+ const state = await jobs.job.state('queue', 'job-id')
522
+ console.log('Job state:', state)
523
+
524
+ // Get job logs
525
+ const logs = await jobs.job.logs('queue', 'job-id')
526
+ console.log('Job logs:', logs)
527
+
528
+ // Subscribe to all events
529
+ await jobs.subscribe('*', (event) => {
530
+ console.log('Event:', event.type, event.data)
531
+ })
532
+ ```
533
+
534
+ ---
535
+
536
+ ## 11. Dependencies
537
+
538
+ ### Peer Dependencies
539
+
540
+ | Package | Version | Purpose |
541
+ |---------|---------|---------|
542
+ | `bullmq` | `^5.0.0` | Job queue implementation |
543
+ | `ioredis` | `^5.0.0` | Redis client |
544
+ | `zod` | `^3.0.0` | Schema validation |
545
+ | `@igniter-js/telemetry` | `^0.1.0` | Optional tracing |
546
+
547
+ ### Dev Dependencies
548
+
549
+ | Package | Version | Purpose |
550
+ |---------|---------|---------|
551
+ | `typescript` | `^5.0.0` | TypeScript compiler |
552
+ | `vitest` | `^1.0.0` | Testing framework |
553
+ | `tsup` | `^8.0.0` | Build tool |
554
+
555
+ ---
556
+
557
+ **Remember:** Always follow existing patterns. When in doubt, check how similar features are implemented in `@igniter-js/store` or `@igniter-js/core`.