servcraft 0.1.0 → 0.1.3
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/.claude/settings.local.json +30 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/LICENSE +21 -0
- package/README.md +1102 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
export type JobStatus = 'waiting' | 'active' | 'completed' | 'failed' | 'delayed' | 'paused';
|
|
2
|
+
|
|
3
|
+
export type JobPriority = 'low' | 'normal' | 'high' | 'critical';
|
|
4
|
+
|
|
5
|
+
export interface JobOptions {
|
|
6
|
+
/** Job priority */
|
|
7
|
+
priority?: JobPriority;
|
|
8
|
+
/** Delay in milliseconds before job starts */
|
|
9
|
+
delay?: number;
|
|
10
|
+
/** Number of retry attempts */
|
|
11
|
+
attempts?: number;
|
|
12
|
+
/** Backoff strategy for retries */
|
|
13
|
+
backoff?: {
|
|
14
|
+
type: 'fixed' | 'exponential';
|
|
15
|
+
delay: number;
|
|
16
|
+
};
|
|
17
|
+
/** Remove job on completion */
|
|
18
|
+
removeOnComplete?: boolean | number;
|
|
19
|
+
/** Remove job on failure */
|
|
20
|
+
removeOnFail?: boolean | number;
|
|
21
|
+
/** Job timeout in milliseconds */
|
|
22
|
+
timeout?: number;
|
|
23
|
+
/** Repeat job on cron schedule */
|
|
24
|
+
repeat?: {
|
|
25
|
+
cron?: string;
|
|
26
|
+
every?: number;
|
|
27
|
+
limit?: number;
|
|
28
|
+
immediately?: boolean;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Job<T = any> {
|
|
33
|
+
/** Unique job ID */
|
|
34
|
+
id: string;
|
|
35
|
+
/** Queue name */
|
|
36
|
+
queueName: string;
|
|
37
|
+
/** Job name/type */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Job data */
|
|
40
|
+
data: T;
|
|
41
|
+
/** Job options */
|
|
42
|
+
options: JobOptions;
|
|
43
|
+
/** Job status */
|
|
44
|
+
status: JobStatus;
|
|
45
|
+
/** Progress (0-100) */
|
|
46
|
+
progress?: number;
|
|
47
|
+
/** Number of attempts made */
|
|
48
|
+
attemptsMade: number;
|
|
49
|
+
/** Job result if completed */
|
|
50
|
+
result?: any;
|
|
51
|
+
/** Error if failed */
|
|
52
|
+
error?: string;
|
|
53
|
+
/** Stack trace if failed */
|
|
54
|
+
stacktrace?: string[];
|
|
55
|
+
/** Creation timestamp */
|
|
56
|
+
createdAt: Date;
|
|
57
|
+
/** Processing start timestamp */
|
|
58
|
+
processedAt?: Date;
|
|
59
|
+
/** Completion timestamp */
|
|
60
|
+
completedAt?: Date;
|
|
61
|
+
/** Failure timestamp */
|
|
62
|
+
failedAt?: Date;
|
|
63
|
+
/** Delay until (for delayed jobs) */
|
|
64
|
+
delayedUntil?: Date;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface QueueConfig {
|
|
68
|
+
/** Redis connection options */
|
|
69
|
+
redis?: {
|
|
70
|
+
host?: string;
|
|
71
|
+
port?: number;
|
|
72
|
+
password?: string;
|
|
73
|
+
db?: number;
|
|
74
|
+
};
|
|
75
|
+
/** Default job options */
|
|
76
|
+
defaultJobOptions?: JobOptions;
|
|
77
|
+
/** Queue prefix */
|
|
78
|
+
prefix?: string;
|
|
79
|
+
/** Enable metrics */
|
|
80
|
+
metrics?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface QueueStats {
|
|
84
|
+
/** Queue name */
|
|
85
|
+
name: string;
|
|
86
|
+
/** Waiting jobs count */
|
|
87
|
+
waiting: number;
|
|
88
|
+
/** Active jobs count */
|
|
89
|
+
active: number;
|
|
90
|
+
/** Completed jobs count */
|
|
91
|
+
completed: number;
|
|
92
|
+
/** Failed jobs count */
|
|
93
|
+
failed: number;
|
|
94
|
+
/** Delayed jobs count */
|
|
95
|
+
delayed: number;
|
|
96
|
+
/** Paused jobs count */
|
|
97
|
+
paused: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface Worker<T = any> {
|
|
101
|
+
/** Worker name/type */
|
|
102
|
+
name: string;
|
|
103
|
+
/** Worker function */
|
|
104
|
+
process: (job: Job<T>) => Promise<any>;
|
|
105
|
+
/** Concurrency (parallel jobs) */
|
|
106
|
+
concurrency?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface CronJob {
|
|
110
|
+
/** Unique cron job ID */
|
|
111
|
+
id: string;
|
|
112
|
+
/** Cron job name */
|
|
113
|
+
name: string;
|
|
114
|
+
/** Cron expression */
|
|
115
|
+
cron: string;
|
|
116
|
+
/** Job data */
|
|
117
|
+
data?: any;
|
|
118
|
+
/** Queue to add job to */
|
|
119
|
+
queueName: string;
|
|
120
|
+
/** Job name/type */
|
|
121
|
+
jobName: string;
|
|
122
|
+
/** Whether the cron is enabled */
|
|
123
|
+
enabled: boolean;
|
|
124
|
+
/** Last run timestamp */
|
|
125
|
+
lastRun?: Date;
|
|
126
|
+
/** Next run timestamp */
|
|
127
|
+
nextRun?: Date;
|
|
128
|
+
/** Creation timestamp */
|
|
129
|
+
createdAt: Date;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface EmailJobData {
|
|
133
|
+
to: string | string[];
|
|
134
|
+
subject: string;
|
|
135
|
+
html?: string;
|
|
136
|
+
text?: string;
|
|
137
|
+
from?: string;
|
|
138
|
+
cc?: string | string[];
|
|
139
|
+
bcc?: string | string[];
|
|
140
|
+
attachments?: Array<{
|
|
141
|
+
filename: string;
|
|
142
|
+
path?: string;
|
|
143
|
+
content?: Buffer | string;
|
|
144
|
+
}>;
|
|
145
|
+
template?: {
|
|
146
|
+
name: string;
|
|
147
|
+
data: Record<string, any>;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface ImageProcessingJobData {
|
|
152
|
+
/** Source image path or URL */
|
|
153
|
+
source: string;
|
|
154
|
+
/** Operations to perform */
|
|
155
|
+
operations: Array<{
|
|
156
|
+
type: 'resize' | 'crop' | 'rotate' | 'watermark' | 'compress';
|
|
157
|
+
options: Record<string, any>;
|
|
158
|
+
}>;
|
|
159
|
+
/** Output path */
|
|
160
|
+
output: string;
|
|
161
|
+
/** Output format */
|
|
162
|
+
format?: 'jpg' | 'png' | 'webp' | 'avif';
|
|
163
|
+
/** Quality (1-100) */
|
|
164
|
+
quality?: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface NotificationJobData {
|
|
168
|
+
userId: string;
|
|
169
|
+
type: 'push' | 'sms' | 'email';
|
|
170
|
+
title?: string;
|
|
171
|
+
message: string;
|
|
172
|
+
data?: Record<string, any>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface WebhookJobData {
|
|
176
|
+
url: string;
|
|
177
|
+
method?: 'POST' | 'PUT' | 'PATCH';
|
|
178
|
+
headers?: Record<string, string>;
|
|
179
|
+
body: Record<string, any>;
|
|
180
|
+
retries?: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface QueueMetrics {
|
|
184
|
+
/** Total jobs processed */
|
|
185
|
+
totalProcessed: number;
|
|
186
|
+
/** Total jobs failed */
|
|
187
|
+
totalFailed: number;
|
|
188
|
+
/** Average processing time (ms) */
|
|
189
|
+
avgProcessingTime: number;
|
|
190
|
+
/** Jobs per minute */
|
|
191
|
+
throughput: number;
|
|
192
|
+
/** Peak concurrency */
|
|
193
|
+
peakConcurrency: number;
|
|
194
|
+
/** Current concurrency */
|
|
195
|
+
currentConcurrency: number;
|
|
196
|
+
/** Success rate (0-100) */
|
|
197
|
+
successRate: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface BulkJobOptions {
|
|
201
|
+
jobs: Array<{
|
|
202
|
+
name: string;
|
|
203
|
+
data: any;
|
|
204
|
+
opts?: JobOptions;
|
|
205
|
+
}>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface JobFilter {
|
|
209
|
+
status?: JobStatus | JobStatus[];
|
|
210
|
+
name?: string;
|
|
211
|
+
limit?: number;
|
|
212
|
+
offset?: number;
|
|
213
|
+
startDate?: Date;
|
|
214
|
+
endDate?: Date;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface JobEvent {
|
|
218
|
+
event: 'added' | 'active' | 'completed' | 'failed' | 'progress' | 'stalled' | 'removed';
|
|
219
|
+
jobId: string;
|
|
220
|
+
data?: any;
|
|
221
|
+
timestamp: Date;
|
|
222
|
+
}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { logger } from '../../core/logger.js';
|
|
2
|
+
import type {
|
|
3
|
+
Worker,
|
|
4
|
+
Job,
|
|
5
|
+
EmailJobData,
|
|
6
|
+
ImageProcessingJobData,
|
|
7
|
+
NotificationJobData,
|
|
8
|
+
WebhookJobData,
|
|
9
|
+
} from './types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Email Worker
|
|
13
|
+
* Sends emails asynchronously
|
|
14
|
+
*/
|
|
15
|
+
export const emailWorker: Worker<EmailJobData> = {
|
|
16
|
+
name: 'send-email',
|
|
17
|
+
concurrency: 5,
|
|
18
|
+
async process(job: Job<EmailJobData>): Promise<{ messageId: string }> {
|
|
19
|
+
const { to, subject } = job.data;
|
|
20
|
+
|
|
21
|
+
logger.info({ jobId: job.id, to, subject }, 'Processing email job');
|
|
22
|
+
|
|
23
|
+
// Simulate email sending
|
|
24
|
+
// In production, integrate with your email service (nodemailer, SendGrid, etc.)
|
|
25
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
26
|
+
|
|
27
|
+
const messageId = `<${Date.now()}@servcraft.local>`;
|
|
28
|
+
|
|
29
|
+
logger.info({ jobId: job.id, messageId, to }, 'Email sent successfully');
|
|
30
|
+
|
|
31
|
+
return { messageId };
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Image Processing Worker
|
|
37
|
+
* Processes images (resize, crop, watermark, etc.)
|
|
38
|
+
*/
|
|
39
|
+
export const imageProcessingWorker: Worker<ImageProcessingJobData> = {
|
|
40
|
+
name: 'process-image',
|
|
41
|
+
concurrency: 3,
|
|
42
|
+
async process(job: Job<ImageProcessingJobData>): Promise<{ output: string; size: number }> {
|
|
43
|
+
const { source, operations, output } = job.data;
|
|
44
|
+
|
|
45
|
+
logger.info({ jobId: job.id, source, operations: operations.length }, 'Processing image job');
|
|
46
|
+
|
|
47
|
+
// Simulate image processing
|
|
48
|
+
// In production, use Sharp, Jimp, or similar library
|
|
49
|
+
for (const operation of operations) {
|
|
50
|
+
logger.debug(
|
|
51
|
+
{ jobId: job.id, operation: operation.type },
|
|
52
|
+
`Applying ${operation.type} operation`
|
|
53
|
+
);
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const outputPath = output;
|
|
58
|
+
const fileSize = Math.floor(Math.random() * 1000000); // Mock file size
|
|
59
|
+
|
|
60
|
+
logger.info({ jobId: job.id, output: outputPath, size: fileSize }, 'Image processed');
|
|
61
|
+
|
|
62
|
+
return { output: outputPath, size: fileSize };
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Notification Worker
|
|
68
|
+
* Sends push notifications, SMS, or emails
|
|
69
|
+
*/
|
|
70
|
+
export const notificationWorker: Worker<NotificationJobData> = {
|
|
71
|
+
name: 'send-notification',
|
|
72
|
+
concurrency: 10,
|
|
73
|
+
async process(job: Job<NotificationJobData>): Promise<{ sent: boolean; notificationId: string }> {
|
|
74
|
+
const { userId, type } = job.data;
|
|
75
|
+
|
|
76
|
+
logger.info({ jobId: job.id, userId, type }, 'Processing notification job');
|
|
77
|
+
|
|
78
|
+
// Simulate notification sending
|
|
79
|
+
// In production, integrate with FCM, SNS, Twilio, etc.
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
81
|
+
|
|
82
|
+
const notificationId = `notif_${Date.now()}`;
|
|
83
|
+
|
|
84
|
+
logger.info({ jobId: job.id, userId, notificationId }, 'Notification sent');
|
|
85
|
+
|
|
86
|
+
return { sent: true, notificationId };
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Webhook Worker
|
|
92
|
+
* Sends HTTP webhooks to external URLs
|
|
93
|
+
*/
|
|
94
|
+
export const webhookWorker: Worker<WebhookJobData> = {
|
|
95
|
+
name: 'send-webhook',
|
|
96
|
+
concurrency: 5,
|
|
97
|
+
async process(job: Job<WebhookJobData>): Promise<{ statusCode: number; response: unknown }> {
|
|
98
|
+
const { url, method = 'POST', headers = {}, body } = job.data;
|
|
99
|
+
|
|
100
|
+
logger.info({ jobId: job.id, url, method }, 'Processing webhook job');
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const response = await fetch(url, {
|
|
104
|
+
method,
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'application/json',
|
|
107
|
+
...headers,
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify(body),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const responseData = await response.text().catch(() => '');
|
|
113
|
+
|
|
114
|
+
logger.info({ jobId: job.id, url, statusCode: response.status }, 'Webhook sent successfully');
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
statusCode: response.status,
|
|
118
|
+
response: responseData,
|
|
119
|
+
};
|
|
120
|
+
} catch (error) {
|
|
121
|
+
logger.error({ jobId: job.id, url, error }, 'Webhook failed');
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Data Export Worker
|
|
129
|
+
* Exports data to CSV, Excel, PDF, etc.
|
|
130
|
+
*/
|
|
131
|
+
export const dataExportWorker: Worker<{
|
|
132
|
+
type: 'csv' | 'excel' | 'pdf';
|
|
133
|
+
data: unknown[];
|
|
134
|
+
filename: string;
|
|
135
|
+
options?: Record<string, unknown>;
|
|
136
|
+
}> = {
|
|
137
|
+
name: 'export-data',
|
|
138
|
+
concurrency: 2,
|
|
139
|
+
async process(job): Promise<{ filename: string; size: number; url: string }> {
|
|
140
|
+
const { type, data, filename } = job.data;
|
|
141
|
+
|
|
142
|
+
logger.info({ jobId: job.id, type, rows: data.length, filename }, 'Processing export job');
|
|
143
|
+
|
|
144
|
+
// Simulate data export
|
|
145
|
+
// In production, use libraries like csv-writer, exceljs, pdfkit
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
147
|
+
|
|
148
|
+
const outputPath = `/exports/${filename}`;
|
|
149
|
+
const fileSize = data.length * 100; // Mock file size
|
|
150
|
+
const downloadUrl = `https://example.com${outputPath}`;
|
|
151
|
+
|
|
152
|
+
logger.info({ jobId: job.id, filename, size: fileSize }, 'Data exported');
|
|
153
|
+
|
|
154
|
+
return { filename: outputPath, size: fileSize, url: downloadUrl };
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Report Generation Worker
|
|
160
|
+
* Generates reports (analytics, financial, etc.)
|
|
161
|
+
*/
|
|
162
|
+
export const reportGenerationWorker: Worker<{
|
|
163
|
+
reportType: string;
|
|
164
|
+
parameters: Record<string, unknown>;
|
|
165
|
+
format: 'pdf' | 'html' | 'excel';
|
|
166
|
+
}> = {
|
|
167
|
+
name: 'generate-report',
|
|
168
|
+
concurrency: 2,
|
|
169
|
+
async process(job): Promise<{ reportUrl: string; generatedAt: Date }> {
|
|
170
|
+
const { reportType, format } = job.data;
|
|
171
|
+
|
|
172
|
+
logger.info({ jobId: job.id, reportType, format }, 'Processing report generation job');
|
|
173
|
+
|
|
174
|
+
// Simulate report generation
|
|
175
|
+
// In production, integrate with your reporting engine
|
|
176
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
177
|
+
|
|
178
|
+
const reportUrl = `/reports/${reportType}_${Date.now()}.${format}`;
|
|
179
|
+
|
|
180
|
+
logger.info({ jobId: job.id, reportType, reportUrl }, 'Report generated');
|
|
181
|
+
|
|
182
|
+
return { reportUrl, generatedAt: new Date() };
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Database Backup Worker
|
|
188
|
+
* Creates database backups
|
|
189
|
+
*/
|
|
190
|
+
export const databaseBackupWorker: Worker<{
|
|
191
|
+
databases: string[];
|
|
192
|
+
destination: string;
|
|
193
|
+
compress?: boolean;
|
|
194
|
+
}> = {
|
|
195
|
+
name: 'database-backup',
|
|
196
|
+
concurrency: 1, // Only one backup at a time
|
|
197
|
+
async process(
|
|
198
|
+
job
|
|
199
|
+
): Promise<{ backups: Array<{ database: string; size: number; path: string }> }> {
|
|
200
|
+
const { databases, destination, compress = true } = job.data;
|
|
201
|
+
|
|
202
|
+
logger.info(
|
|
203
|
+
{ jobId: job.id, databases, destination, compress },
|
|
204
|
+
'Processing database backup job'
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const backups = [];
|
|
208
|
+
|
|
209
|
+
for (const database of databases) {
|
|
210
|
+
logger.debug({ jobId: job.id, database }, `Backing up database: ${database}`);
|
|
211
|
+
|
|
212
|
+
// Simulate backup
|
|
213
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
214
|
+
|
|
215
|
+
const backupPath = `${destination}/${database}_${Date.now()}.sql${compress ? '.gz' : ''}`;
|
|
216
|
+
const size = Math.floor(Math.random() * 50000000); // Mock size
|
|
217
|
+
|
|
218
|
+
backups.push({ database, size, path: backupPath });
|
|
219
|
+
|
|
220
|
+
logger.info({ jobId: job.id, database, size, path: backupPath }, 'Database backed up');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { backups };
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Cache Warming Worker
|
|
229
|
+
* Warms up cache with frequently accessed data
|
|
230
|
+
*/
|
|
231
|
+
export const cacheWarmingWorker: Worker<{
|
|
232
|
+
keys: string[];
|
|
233
|
+
ttl?: number;
|
|
234
|
+
}> = {
|
|
235
|
+
name: 'warm-cache',
|
|
236
|
+
concurrency: 5,
|
|
237
|
+
async process(job): Promise<{ warmed: number; failed: number }> {
|
|
238
|
+
const { keys, ttl = 3600 } = job.data;
|
|
239
|
+
|
|
240
|
+
logger.info({ jobId: job.id, keys: keys.length, ttl }, 'Processing cache warming job');
|
|
241
|
+
|
|
242
|
+
let warmed = 0;
|
|
243
|
+
let failed = 0;
|
|
244
|
+
|
|
245
|
+
for (const key of keys) {
|
|
246
|
+
try {
|
|
247
|
+
// Simulate cache warming
|
|
248
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
249
|
+
warmed++;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
failed++;
|
|
252
|
+
logger.warn({ jobId: job.id, key, error }, 'Failed to warm cache key');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
logger.info({ jobId: job.id, warmed, failed }, 'Cache warming completed');
|
|
257
|
+
|
|
258
|
+
return { warmed, failed };
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Data Cleanup Worker
|
|
264
|
+
* Cleans up old data (logs, temp files, etc.)
|
|
265
|
+
*/
|
|
266
|
+
export const dataCleanupWorker: Worker<{
|
|
267
|
+
targets: Array<{
|
|
268
|
+
type: 'database' | 'files' | 'logs';
|
|
269
|
+
table?: string;
|
|
270
|
+
directory?: string;
|
|
271
|
+
olderThanDays: number;
|
|
272
|
+
}>;
|
|
273
|
+
}> = {
|
|
274
|
+
name: 'cleanup-data',
|
|
275
|
+
concurrency: 1,
|
|
276
|
+
async process(job): Promise<{ cleaned: Array<{ type: string; count: number; size: number }> }> {
|
|
277
|
+
const { targets } = job.data;
|
|
278
|
+
|
|
279
|
+
logger.info({ jobId: job.id, targets: targets.length }, 'Processing data cleanup job');
|
|
280
|
+
|
|
281
|
+
const cleaned = [];
|
|
282
|
+
|
|
283
|
+
for (const target of targets) {
|
|
284
|
+
logger.debug({ jobId: job.id, target }, `Cleaning up ${target.type}`);
|
|
285
|
+
|
|
286
|
+
// Simulate cleanup
|
|
287
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
288
|
+
|
|
289
|
+
const count = Math.floor(Math.random() * 1000);
|
|
290
|
+
const size = count * 1024; // Mock size
|
|
291
|
+
|
|
292
|
+
cleaned.push({
|
|
293
|
+
type: target.type,
|
|
294
|
+
count,
|
|
295
|
+
size,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
logger.info({ jobId: job.id, target: target.type, count, size }, 'Cleanup completed');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { cleaned };
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Batch Processing Worker
|
|
307
|
+
* Processes items in batches
|
|
308
|
+
*/
|
|
309
|
+
export const batchProcessingWorker: Worker<{
|
|
310
|
+
items: unknown[];
|
|
311
|
+
batchSize: number;
|
|
312
|
+
processor: 'validate' | 'transform' | 'import';
|
|
313
|
+
}> = {
|
|
314
|
+
name: 'batch-process',
|
|
315
|
+
concurrency: 3,
|
|
316
|
+
async process(job): Promise<{ processed: number; failed: number; errors: unknown[] }> {
|
|
317
|
+
const { items, batchSize, processor } = job.data;
|
|
318
|
+
|
|
319
|
+
logger.info(
|
|
320
|
+
{ jobId: job.id, items: items.length, batchSize, processor },
|
|
321
|
+
'Processing batch job'
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
let processed = 0;
|
|
325
|
+
let failed = 0;
|
|
326
|
+
const errors = [];
|
|
327
|
+
|
|
328
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
329
|
+
const batch = items.slice(i, i + batchSize);
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
// Simulate batch processing
|
|
333
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
334
|
+
processed += batch.length;
|
|
335
|
+
|
|
336
|
+
// Update progress
|
|
337
|
+
const progress = Math.floor(((i + batch.length) / items.length) * 100);
|
|
338
|
+
logger.debug({ jobId: job.id, progress }, `Batch progress: ${progress}%`);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
failed += batch.length;
|
|
341
|
+
errors.push({ batch: i / batchSize, error });
|
|
342
|
+
logger.warn({ jobId: job.id, batch: i / batchSize, error }, 'Batch failed');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
logger.info({ jobId: job.id, processed, failed }, 'Batch processing completed');
|
|
347
|
+
|
|
348
|
+
return { processed, failed, errors };
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Export all default workers
|
|
354
|
+
*/
|
|
355
|
+
export const defaultWorkers = [
|
|
356
|
+
emailWorker,
|
|
357
|
+
imageProcessingWorker,
|
|
358
|
+
notificationWorker,
|
|
359
|
+
webhookWorker,
|
|
360
|
+
dataExportWorker,
|
|
361
|
+
reportGenerationWorker,
|
|
362
|
+
databaseBackupWorker,
|
|
363
|
+
cacheWarmingWorker,
|
|
364
|
+
dataCleanupWorker,
|
|
365
|
+
batchProcessingWorker,
|
|
366
|
+
];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiting Module
|
|
3
|
+
*
|
|
4
|
+
* Provides advanced rate limiting capabilities with multiple algorithms:
|
|
5
|
+
* - Fixed Window: Simple, but can allow bursts at boundaries
|
|
6
|
+
* - Sliding Window: More accurate, prevents boundary bursts
|
|
7
|
+
* - Token Bucket: Allows controlled bursts while enforcing average rate
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - IP-based, user-based, or custom key rate limiting
|
|
11
|
+
* - Whitelist/blacklist support
|
|
12
|
+
* - Custom limits per endpoint or user role
|
|
13
|
+
* - Memory or Redis storage
|
|
14
|
+
* - Admin routes for management
|
|
15
|
+
* - Standard rate limit headers
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { createRateLimiter, RateLimitService } from './modules/rate-limit';
|
|
20
|
+
*
|
|
21
|
+
* // Basic usage
|
|
22
|
+
* app.use(createRateLimiter({
|
|
23
|
+
* max: 100,
|
|
24
|
+
* windowMs: 60 * 1000, // 1 minute
|
|
25
|
+
* algorithm: 'sliding-window'
|
|
26
|
+
* }));
|
|
27
|
+
*
|
|
28
|
+
* // Pre-configured limiters
|
|
29
|
+
* import { strictRateLimit, authRateLimit } from './modules/rate-limit';
|
|
30
|
+
* app.post('/login', authRateLimit, loginHandler);
|
|
31
|
+
* app.post('/sensitive', strictRateLimit, sensitiveHandler);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// Types
|
|
36
|
+
export * from './types.js';
|
|
37
|
+
|
|
38
|
+
// Service
|
|
39
|
+
export { RateLimitService } from './rate-limit.service.js';
|
|
40
|
+
|
|
41
|
+
// Middleware
|
|
42
|
+
export {
|
|
43
|
+
createRateLimiter,
|
|
44
|
+
rateLimitEndpoint,
|
|
45
|
+
strictRateLimit,
|
|
46
|
+
standardRateLimit,
|
|
47
|
+
relaxedRateLimit,
|
|
48
|
+
authRateLimit,
|
|
49
|
+
ipRateLimit,
|
|
50
|
+
userRateLimit,
|
|
51
|
+
apiKeyRateLimit,
|
|
52
|
+
} from './rate-limit.middleware.js';
|
|
53
|
+
|
|
54
|
+
// Routes
|
|
55
|
+
export { createRateLimitRoutes } from './rate-limit.routes.js';
|
|
56
|
+
|
|
57
|
+
// Stores
|
|
58
|
+
export { MemoryStore } from './stores/memory.store.js';
|
|
59
|
+
export { RedisStore } from './stores/redis.store.js';
|