@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.ko.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.md) | [한국어](#)
9
+
10
+ **[Sidequest.js](https://sidequestjs.com/)용 NestJS 통합 라이브러리** - Redis 없이 데이터베이스 기반 백그라운드 Job 처리.
11
+
12
+ 기존 데이터베이스(PostgreSQL, MySQL, MongoDB, SQLite)를 활용해 백그라운드 Job을 처리하세요. NestJS 데코레이터와 선택적 CLS 통합을 완벽 지원합니다.
13
+
14
+ ## 특징
15
+
16
+ - **데이터베이스 네이티브 Job** - Redis 대신 기존 데이터베이스 사용
17
+ - **트랜잭션 일관성** - 데이터베이스 트랜잭션 내에서 원자적 Job 생성
18
+ - **데코레이터 기반 API** - 익숙한 `@Processor`, `@OnJob`, `@Retry` 데코레이터
19
+ - **선택적 CLS 지원** - nestjs-cls로 컨텍스트 전파
20
+ - **이벤트 핸들러** - Job 생명주기 이벤트용 `@OnJobComplete`, `@OnJobFailed`
21
+ - **다중 큐 지원** - 개별 동시성 설정으로 여러 큐 구성
22
+ - **대시보드** - Job 모니터링을 위한 내장 UI (선택 사항)
23
+
24
+ ## 설치
25
+
26
+ ```bash
27
+ npm install @m16khb/nestjs-sidequest sidequest
28
+
29
+ # 데이터베이스 백엔드 선택
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
+ ### 선택적 의존성
37
+
38
+ ```bash
39
+ # CLS 컨텍스트 전파를 위해
40
+ pnpm add nestjs-cls
41
+ ```
42
+
43
+ ## 요구사항
44
+
45
+ - Node.js >= 22.6.0
46
+ - NestJS >= 10.0.0
47
+ - TypeScript >= 5.7
48
+ - 지원되는 데이터베이스 백엔드
49
+
50
+ ## 빠른 시작
51
+
52
+ ### 1. 모듈 등록
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. Job 클래스 정의 (Sidequest.js 패턴)
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. 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: `${job.name}님, 환영합니다!`,
109
+ template: 'welcome',
110
+ });
111
+ return { sentAt: new Date() };
112
+ }
113
+
114
+ @OnJobComplete(SendWelcomeEmailJob)
115
+ async onComplete(event: JobCompleteEvent) {
116
+ console.log(`이메일 발송 완료: ${event.result.sentAt}`);
117
+ }
118
+
119
+ @OnJobFailed(SendWelcomeEmailJob)
120
+ async onFailed(event: JobFailedEvent) {
121
+ console.error(`이메일 발송 실패: ${event.error.message}`);
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### 4. Queue 주입 및 Job 추가
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
+ // 데이터베이스에 사용자 생성...
142
+ const user = await this.userRepository.save({ email, name });
143
+
144
+ // 환영 이메일 큐에 추가 (백그라운드 실행)
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
+ ## 모듈 설정
160
+
161
+ ### forRoot (동기)
162
+
163
+ ```typescript
164
+ SidequestModule.forRoot({
165
+ // 모듈
166
+ isGlobal: true, // 기본값: true
167
+
168
+ // 데이터베이스 백엔드
169
+ backend: {
170
+ driver: '@sidequest/postgres-backend',
171
+ config: process.env.DATABASE_URL,
172
+ },
173
+
174
+ // 큐
175
+ queues: [
176
+ {
177
+ name: 'email',
178
+ concurrency: 5, // 최대 동시 Job 수
179
+ priority: 50, // 기본 우선순위 (높을수록 우선)
180
+ state: 'active', // 'active' | 'paused'
181
+ },
182
+ ],
183
+
184
+ // 엔진 설정
185
+ maxConcurrentJobs: 10, // 전체 최대 동시성
186
+ minThreads: 4, // 최소 워커 스레드 (기본값: CPU 코어 수)
187
+ maxThreads: 8, // 최대 워커 스레드 (기본값: minThreads * 2)
188
+ jobPollingInterval: 100, // Job 폴링 간격 (ms)
189
+ releaseStaleJobsIntervalMin: 60, // 오래된 Job 해제 간격 (분)
190
+ cleanupFinishedJobsIntervalMin: 60, // 완료된 Job 정리 간격 (분)
191
+
192
+ // 로거
193
+ logger: {
194
+ level: 'info',
195
+ json: false, // 프로덕션용 JSON 출력
196
+ },
197
+
198
+ // 대시보드 (선택 사항)
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초
213
+ },
214
+
215
+ // CLS 통합 (선택 사항)
216
+ enableCls: true, // nestjs-cls 필요
217
+ })
218
+ ```
219
+
220
+ ### forRootAsync (비동기)
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
+ ## 데코레이터
240
+
241
+ ### @Processor(queueName, options?)
242
+
243
+ 클래스를 지정된 큐의 Job 프로세서로标记합니다.
244
+
245
+ ```typescript
246
+ @Processor('email', { concurrency: 10 })
247
+ export class EmailProcessor {}
248
+ ```
249
+
250
+ ### @OnJob(JobClass, options?)
251
+
252
+ 메서드를 지정된 Job 타입의 핸들러로标记합니다.
253
+
254
+ ```typescript
255
+ @OnJob(SendEmailJob, { timeout: 30000 })
256
+ async handleEmail(job: SendEmailJob) {
257
+ // ...
258
+ }
259
+ ```
260
+
261
+ ### @Retry(options)
262
+
263
+ Job 핸들러의 재시도 정책을 설정합니다.
264
+
265
+ ```typescript
266
+ @Retry({
267
+ maxAttempts: 3,
268
+ backoff: {
269
+ type: 'exponential', // 'exponential' | 'fixed'
270
+ delay: 1000, // 초기 지연 시간 (ms)
271
+ multiplier: 2, // 지수 승수
272
+ },
273
+ retryOn: ['NetworkError', 'TimeoutError'], // 이 에러들만 재시도
274
+ })
275
+ async handleJob(job: AnyJob) {
276
+ // ...
277
+ }
278
+ ```
279
+
280
+ ### @InjectQueue(queueName)
281
+
282
+ 큐 서비스 인스턴스를 주입합니다.
283
+
284
+ ```typescript
285
+ constructor(@InjectQueue('email') private emailQueue: IQueueService) {}
286
+ ```
287
+
288
+ ### @OnJobComplete(JobClass?)
289
+
290
+ Job이 성공적으로 완료되었을 때 호출되는 핸들러입니다.
291
+
292
+ ```typescript
293
+ @OnJobComplete(SendEmailJob)
294
+ async onComplete(event: JobCompleteEvent) {
295
+ console.log(`Job ${event.jobId} 완료:`, event.result);
296
+ }
297
+ ```
298
+
299
+ ### @OnJobFailed(JobClass?)
300
+
301
+ Job이 실패했을 때 호출되는 핸들러입니다.
302
+
303
+ ```typescript
304
+ @OnJobFailed(SendEmailJob)
305
+ async onFailed(event: JobFailedEvent) {
306
+ console.error(`Job ${event.jobId} 실패:`, event.error);
307
+ }
308
+ ```
309
+
310
+ ## 큐 서비스 API
311
+
312
+ ```typescript
313
+ interface IQueueService {
314
+ readonly name: string;
315
+
316
+ // 단일 Job 추가
317
+ add<T>(JobClass: new (...args: unknown[]) => T, ...args: Parameters<T['constructor']>): Promise<string>;
318
+
319
+ // 옵션과 함께 Job 추가
320
+ addWithOptions<T>(
321
+ JobClass: new (...args: unknown[]) => T,
322
+ options: JobAddOptions,
323
+ ...args: Parameters<T['constructor']>
324
+ ): Promise<string>;
325
+
326
+ // 예약된 Job 추가
327
+ addScheduled<T>(
328
+ JobClass: new (...args: unknown[]) => T,
329
+ scheduledAt: Date,
330
+ ...args: Parameters<T['constructor']>
331
+ ): Promise<string>;
332
+
333
+ // 여러 Job 추가 (벌크)
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; // 높을수록 먼저 처리 (기본값: 50)
347
+ timeout?: number; // Job 타임아웃 (ms)
348
+ maxAttempts?: number; // 재시도 횟수 재정의
349
+ startAfter?: Date; // 지연된 시작
350
+ }
351
+ ```
352
+
353
+ ## CLS 통합
354
+
355
+ CLS 통합을 활성화하여 Job 실행 간 컨텍스트(traceId, userId 등)를 전파하세요:
356
+
357
+ ```typescript
358
+ // app.module.ts
359
+ SidequestModule.forRoot({
360
+ // ...
361
+ enableCls: true, // nestjs-cls 필요
362
+ })
363
+
364
+ // 컨텍스트가 자동으로 전파됨
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}] 사용자 ${userId}의 Job 처리 중`);
375
+ }
376
+ }
377
+ ```
378
+
379
+ ## 왜 Sidequest.js인가요?
380
+
381
+ | 기능 | BullMQ + Redis | Sidequest.js |
382
+ |---------|----------------|--------------|
383
+ | 인프라 | 추가 Redis 서버 필요 | 기존 데이터베이스 사용 |
384
+ | 트랜잭션 지원 | 보상 트랜잭션 필요 | 네이티브 DB 트랜잭션 지원 |
385
+ | 운영 비용 | 추가 Redis 인스턴스 비용 | 추가 인프라 불필요 |
386
+ | 배포 단순성 | Redis 클러스터 관리 | 간단한 데이터베이스 연결 |
387
+
388
+ ## 라이선스
389
+
390
+ **LGPL v3** - 이 라이브러리는 GNU Lesser General Public License v3.0 하에 라이선스됩니다.
391
+
392
+ 의미:
393
+ - 상용 소프트웨어에서 이 라이브러리를 사용해도 소스코드를 공개할 의무가 없습니다
394
+ - 이 라이브러리 자체를 수정한 경우 수정본은 LGPL/GPL로 공개해야 합니다
395
+ - 라이선스 attribution을 제공하고 사용자가 라이브러리를 교체할 수 있도록 해야 합니다
396
+ - 동적 링킹을 권장합니다
397
+
398
+ 전체 라이선스 텍스트는 [LICENSE](LICENSE)를 참고하세요.
399
+
400
+ ---
401
+
402
+ 이 패키지는 LGPL v3로 라이선스된 [Sidequest.js](https://sidequestjs.com/)를 통합합니다.