@m16khb/nestjs-sidequest 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/README.md ADDED
@@ -0,0 +1,402 @@
1
+ # @m16khb/nestjs-sidequest
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@m16khb/nestjs-sidequest.svg)](https://www.npmjs.com/package/@m16khb/nestjs-sidequest)
4
+ [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0.html)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7+-blue.svg)](https://www.typescriptlang.org/)
6
+ [![NestJS](https://img.shields.io/badge/NestJS-10%20%7C%2011-red.svg)](https://nestjs.com/)
7
+
8
+ [English](#) | [한국어](https://github.com/m16khb/npm-library/blob/main/packages/nestjs-sidequest/README.ko.md)
9
+
10
+ **NestJS integration for [Sidequest.js](https://sidequestjs.com/)** - Database-native background job processing without Redis.
11
+
12
+ Process background jobs using your existing database (PostgreSQL, MySQL, MongoDB, SQLite) with full NestJS decorator support and optional CLS integration.
13
+
14
+ ## Features
15
+
16
+ - **Database-Native Jobs** - Use your existing database instead of Redis
17
+ - **Transaction Consistency** - Atomic job creation within database transactions
18
+ - **Decorator-Based API** - Familiar `@Processor`, `@OnJob`, `@Retry` decorators
19
+ - **Optional CLS Support** - Context propagation with nestjs-cls
20
+ - **Event Handlers** - `@OnJobComplete`, `@OnJobFailed` for job lifecycle events
21
+ - **Multiple Queue Support** - Configure queues with individual concurrency settings
22
+ - **Dashboard** - Built-in UI for monitoring jobs (optional)
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install @m16khb/nestjs-sidequest sidequest
28
+
29
+ # Choose your database backend
30
+ npm install @sidequest/postgres-backend # PostgreSQL
31
+ npm install @sidequest/mysql-backend # MySQL
32
+ npm install @sidequest/sqlite-backend # SQLite
33
+ npm install @sidequest/mongo-backend # MongoDB
34
+ ```
35
+
36
+ ### Optional Dependencies
37
+
38
+ ```bash
39
+ # For CLS context propagation
40
+ pnpm add nestjs-cls
41
+ ```
42
+
43
+ ## Requirements
44
+
45
+ - Node.js >= 22.6.0
46
+ - NestJS >= 10.0.0
47
+ - TypeScript >= 5.7
48
+ - A supported database backend
49
+
50
+ ## Quick Start
51
+
52
+ ### 1. Register Module
53
+
54
+ ```typescript
55
+ // app.module.ts
56
+ import { Module } from '@nestjs/common';
57
+ import { SidequestModule } from '@m16khb/nestjs-sidequest';
58
+
59
+ @Module({
60
+ imports: [
61
+ SidequestModule.forRoot({
62
+ backend: {
63
+ driver: '@sidequest/postgres-backend',
64
+ config: process.env.DATABASE_URL,
65
+ },
66
+ queues: [
67
+ { name: 'email', concurrency: 5 },
68
+ { name: 'reports', concurrency: 2 },
69
+ ],
70
+ }),
71
+ ],
72
+ })
73
+ export class AppModule {}
74
+ ```
75
+
76
+ ### 2. Define a Job Class (Sidequest.js Pattern)
77
+
78
+ ```typescript
79
+ // jobs/send-welcome-email.job.ts
80
+ import { Job } from 'sidequest';
81
+
82
+ export class SendWelcomeEmailJob extends Job {
83
+ constructor(
84
+ public readonly to: string,
85
+ public readonly name: string,
86
+ ) {
87
+ super();
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### 3. Create a Processor
93
+
94
+ ```typescript
95
+ // email.processor.ts
96
+ import { Processor, OnJob, Retry, OnJobComplete, OnJobFailed } from '@m16khb/nestjs-sidequest';
97
+ import { SendWelcomeEmailJob } from './jobs/send-welcome-email.job';
98
+
99
+ @Processor('email')
100
+ export class EmailProcessor {
101
+ constructor(private readonly mailer: MailerService) {}
102
+
103
+ @OnJob(SendWelcomeEmailJob)
104
+ @Retry({ maxAttempts: 3, backoff: { type: 'exponential', delay: 1000 } })
105
+ async handleWelcomeEmail(job: SendWelcomeEmailJob) {
106
+ await this.mailer.send({
107
+ to: job.to,
108
+ subject: `Welcome, ${job.name}!`,
109
+ template: 'welcome',
110
+ });
111
+ return { sentAt: new Date() };
112
+ }
113
+
114
+ @OnJobComplete(SendWelcomeEmailJob)
115
+ async onComplete(event: JobCompleteEvent) {
116
+ console.log(`Email sent at: ${event.result.sentAt}`);
117
+ }
118
+
119
+ @OnJobFailed(SendWelcomeEmailJob)
120
+ async onFailed(event: JobFailedEvent) {
121
+ console.error(`Email failed: ${event.error.message}`);
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### 4. Inject Queue and Add Jobs
127
+
128
+ ```typescript
129
+ // user.service.ts
130
+ import { Injectable } from '@nestjs/common';
131
+ import { InjectQueue, IQueueService } from '@m16khb/nestjs-sidequest';
132
+ import { SendWelcomeEmailJob } from './jobs/send-welcome-email.job';
133
+
134
+ @Injectable()
135
+ export class UserService {
136
+ constructor(
137
+ @InjectQueue('email') private emailQueue: IQueueService,
138
+ ) {}
139
+
140
+ async createUser(email: string, name: string) {
141
+ // Create user in database...
142
+ const user = await this.userRepository.save({ email, name });
143
+
144
+ // Queue welcome email (runs in background)
145
+ await this.emailQueue.add(SendWelcomeEmailJob, email, name);
146
+ }
147
+
148
+ async scheduleWelcomeEmail(email: string, name: string, sendAt: Date) {
149
+ await this.emailQueue.addScheduled(
150
+ SendWelcomeEmailJob,
151
+ sendAt,
152
+ email,
153
+ name
154
+ );
155
+ }
156
+ }
157
+ ```
158
+
159
+ ## Module Configuration
160
+
161
+ ### forRoot (Synchronous)
162
+
163
+ ```typescript
164
+ SidequestModule.forRoot({
165
+ // Module
166
+ isGlobal: true, // Default: true
167
+
168
+ // Database Backend
169
+ backend: {
170
+ driver: '@sidequest/postgres-backend',
171
+ config: process.env.DATABASE_URL,
172
+ },
173
+
174
+ // Queues
175
+ queues: [
176
+ {
177
+ name: 'email',
178
+ concurrency: 5, // Max concurrent jobs
179
+ priority: 50, // Default priority (higher = first)
180
+ state: 'active', // 'active' | 'paused'
181
+ },
182
+ ],
183
+
184
+ // Engine Settings
185
+ maxConcurrentJobs: 10, // Global max concurrency
186
+ minThreads: 4, // Min worker threads (default: CPU cores)
187
+ maxThreads: 8, // Max worker threads (default: minThreads * 2)
188
+ jobPollingInterval: 100, // Job polling interval (ms)
189
+ releaseStaleJobsIntervalMin: 60, // Stale job release interval (minutes)
190
+ cleanupFinishedJobsIntervalMin: 60, // Finished job cleanup interval (minutes)
191
+
192
+ // Logger
193
+ logger: {
194
+ level: 'info',
195
+ json: false, // JSON output for production
196
+ },
197
+
198
+ // Dashboard (Optional)
199
+ dashboard: {
200
+ enabled: true,
201
+ port: 8678,
202
+ path: '/',
203
+ auth: {
204
+ user: 'admin',
205
+ password: 'password',
206
+ },
207
+ },
208
+
209
+ // Graceful Shutdown
210
+ gracefulShutdown: {
211
+ enabled: true,
212
+ timeout: 30000, // 30 seconds
213
+ },
214
+
215
+ // CLS Integration (Optional)
216
+ enableCls: true, // Requires nestjs-cls to be installed
217
+ })
218
+ ```
219
+
220
+ ### forRootAsync (Asynchronous)
221
+
222
+ ```typescript
223
+ SidequestModule.forRootAsync({
224
+ imports: [ConfigModule],
225
+ useFactory: (config: ConfigService) => ({
226
+ backend: {
227
+ driver: '@sidequest/postgres-backend',
228
+ config: config.get('DATABASE_URL'),
229
+ },
230
+ queues: [
231
+ { name: 'email', concurrency: config.get('EMAIL_CONCURRENCY', 5) },
232
+ ],
233
+ enableCls: config.get('ENABLE_CLS', false),
234
+ }),
235
+ inject: [ConfigService],
236
+ })
237
+ ```
238
+
239
+ ## Decorators
240
+
241
+ ### @Processor(queueName, options?)
242
+
243
+ Marks a class as a job processor for the specified queue.
244
+
245
+ ```typescript
246
+ @Processor('email', { concurrency: 10 })
247
+ export class EmailProcessor {}
248
+ ```
249
+
250
+ ### @OnJob(JobClass, options?)
251
+
252
+ Marks a method as a handler for the specified job type.
253
+
254
+ ```typescript
255
+ @OnJob(SendEmailJob, { timeout: 30000 })
256
+ async handleEmail(job: SendEmailJob) {
257
+ // ...
258
+ }
259
+ ```
260
+
261
+ ### @Retry(options)
262
+
263
+ Configures retry policy for a job handler.
264
+
265
+ ```typescript
266
+ @Retry({
267
+ maxAttempts: 3,
268
+ backoff: {
269
+ type: 'exponential', // 'exponential' | 'fixed'
270
+ delay: 1000, // Initial delay in ms
271
+ multiplier: 2, // Exponential multiplier
272
+ },
273
+ retryOn: ['NetworkError', 'TimeoutError'], // Retry only on these errors
274
+ })
275
+ async handleJob(job: AnyJob) {
276
+ // ...
277
+ }
278
+ ```
279
+
280
+ ### @InjectQueue(queueName)
281
+
282
+ Injects a queue service instance.
283
+
284
+ ```typescript
285
+ constructor(@InjectQueue('email') private emailQueue: IQueueService) {}
286
+ ```
287
+
288
+ ### @OnJobComplete(JobClass?)
289
+
290
+ Handler called when a job completes successfully.
291
+
292
+ ```typescript
293
+ @OnJobComplete(SendEmailJob)
294
+ async onComplete(event: JobCompleteEvent) {
295
+ console.log(`Job ${event.jobId} completed:`, event.result);
296
+ }
297
+ ```
298
+
299
+ ### @OnJobFailed(JobClass?)
300
+
301
+ Handler called when a job fails.
302
+
303
+ ```typescript
304
+ @OnJobFailed(SendEmailJob)
305
+ async onFailed(event: JobFailedEvent) {
306
+ console.error(`Job ${event.jobId} failed:`, event.error);
307
+ }
308
+ ```
309
+
310
+ ## Queue Service API
311
+
312
+ ```typescript
313
+ interface IQueueService {
314
+ readonly name: string;
315
+
316
+ // Add a single job
317
+ add<T>(JobClass: new (...args: unknown[]) => T, ...args: Parameters<T['constructor']>): Promise<string>;
318
+
319
+ // Add a job with options
320
+ addWithOptions<T>(
321
+ JobClass: new (...args: unknown[]) => T,
322
+ options: JobAddOptions,
323
+ ...args: Parameters<T['constructor']>
324
+ ): Promise<string>;
325
+
326
+ // Add a scheduled job
327
+ addScheduled<T>(
328
+ JobClass: new (...args: unknown[]) => T,
329
+ scheduledAt: Date,
330
+ ...args: Parameters<T['constructor']>
331
+ ): Promise<string>;
332
+
333
+ // Add multiple jobs (bulk)
334
+ addBulk<T>(jobs: Array<{
335
+ JobClass: new (...args: unknown[]) => T;
336
+ args: Parameters<T['constructor']>;
337
+ options?: JobAddOptions;
338
+ }>): Promise<string[]>;
339
+ }
340
+ ```
341
+
342
+ ### JobAddOptions
343
+
344
+ ```typescript
345
+ interface JobAddOptions {
346
+ priority?: number; // Higher = processed first (default: 50)
347
+ timeout?: number; // Job timeout in ms
348
+ maxAttempts?: number; // Override retry attempts
349
+ startAfter?: Date; // Delayed start
350
+ }
351
+ ```
352
+
353
+ ## CLS Integration
354
+
355
+ Enable CLS integration to propagate context (traceId, userId, etc.) across job executions:
356
+
357
+ ```typescript
358
+ // app.module.ts
359
+ SidequestModule.forRoot({
360
+ // ...
361
+ enableCls: true, // Requires nestjs-cls
362
+ })
363
+
364
+ // Context is automatically propagated
365
+ @Processor('email')
366
+ export class EmailProcessor {
367
+ constructor(private readonly cls: ClsService) {}
368
+
369
+ @OnJob(SendEmailJob)
370
+ async handleEmail(job: SendEmailJob) {
371
+ const traceId = this.cls.getId();
372
+ const userId = this.cls.get('userId');
373
+
374
+ console.log(`[${traceId}] Processing job for user ${userId}`);
375
+ }
376
+ }
377
+ ```
378
+
379
+ ## Why Sidequest.js?
380
+
381
+ | Feature | BullMQ + Redis | Sidequest.js |
382
+ |---------|----------------|--------------|
383
+ | Infrastructure | Additional Redis server | Uses existing database |
384
+ | Transaction Support | Requires compensation transactions | Native DB transaction support |
385
+ | Operational Cost | Extra Redis instance cost | No additional infrastructure |
386
+ | Deployment Simplicity | Manage Redis cluster | Simple database connection |
387
+
388
+ ## License
389
+
390
+ **LGPL v3** - This library is licensed under the GNU Lesser General Public License v3.0.
391
+
392
+ This means:
393
+ - You may use this library in proprietary software without opening your source code
394
+ - If you modify this library itself, modifications must be released under LGPL/GPL
395
+ - You must provide license attribution and allow users to replace the library
396
+ - Dynamic linking is recommended for compliance
397
+
398
+ For full license text, see [LICENSE](LICENSE).
399
+
400
+ ---
401
+
402
+ This package integrates [Sidequest.js](https://sidequestjs.com/), which is also licensed under LGPL v3.