@monque/tsed 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/LICENSE +15 -0
- package/README.md +222 -0
- package/dist/CHANGELOG.md +19 -0
- package/dist/LICENSE +15 -0
- package/dist/README.md +222 -0
- package/dist/index.cjs +712 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +582 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +582 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +691 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +98 -0
- package/src/config/config.ts +60 -0
- package/src/config/index.ts +2 -0
- package/src/config/types.ts +114 -0
- package/src/constants/constants.ts +12 -0
- package/src/constants/index.ts +2 -0
- package/src/constants/types.ts +21 -0
- package/src/decorators/cron.ts +58 -0
- package/src/decorators/index.ts +10 -0
- package/src/decorators/types.ts +131 -0
- package/src/decorators/worker-controller.ts +55 -0
- package/src/decorators/worker.ts +66 -0
- package/src/index.ts +20 -0
- package/src/monque-module.ts +199 -0
- package/src/services/index.ts +1 -0
- package/src/services/monque-service.ts +267 -0
- package/src/utils/build-job-name.ts +17 -0
- package/src/utils/collect-worker-metadata.ts +95 -0
- package/src/utils/get-worker-token.ts +27 -0
- package/src/utils/guards.ts +62 -0
- package/src/utils/index.ts +13 -0
- package/src/utils/resolve-database.ts +119 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import { Store, useDecorators } from "@tsed/core";
|
|
2
|
+
import { Configuration, DIContext, Inject, Injectable, InjectorService, LOGGER, Module, ProviderScope, runInContext } from "@tsed/di";
|
|
3
|
+
import { Monque, MonqueError } from "@monque/core";
|
|
4
|
+
import { ObjectId } from "mongodb";
|
|
5
|
+
|
|
6
|
+
//#region src/config/config.ts
|
|
7
|
+
/**
|
|
8
|
+
* Validate that exactly one database resolution strategy is provided.
|
|
9
|
+
*
|
|
10
|
+
* @param config - The configuration to validate.
|
|
11
|
+
* @throws Error if zero or multiple strategies are provided.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* validateDatabaseConfig({ db: mongoDb }); // OK
|
|
16
|
+
* validateDatabaseConfig({}); // throws
|
|
17
|
+
* validateDatabaseConfig({ db: mongoDb, dbFactory: fn }); // throws
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function validateDatabaseConfig(config) {
|
|
21
|
+
const strategies = [
|
|
22
|
+
config.db,
|
|
23
|
+
config.dbFactory,
|
|
24
|
+
config.dbToken
|
|
25
|
+
].filter(Boolean);
|
|
26
|
+
if (strategies.length === 0) throw new Error("MonqueTsedConfig requires exactly one of 'db', 'dbFactory', or 'dbToken' to be set");
|
|
27
|
+
if (strategies.length > 1) throw new Error("MonqueTsedConfig accepts only one of 'db', 'dbFactory', or 'dbToken' - multiple were provided");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/constants/constants.ts
|
|
32
|
+
/**
|
|
33
|
+
* Symbol used to store decorator metadata on class constructors.
|
|
34
|
+
*
|
|
35
|
+
* Used by @WorkerController, @Worker, and @Cron decorators to attach
|
|
36
|
+
* metadata that is later collected by MonqueModule during initialization.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* Store.from(Class).set(MONQUE, metadata);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
const MONQUE = Symbol.for("monque");
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/constants/types.ts
|
|
47
|
+
/**
|
|
48
|
+
* Provider type constants for DI scanning.
|
|
49
|
+
*
|
|
50
|
+
* These constants are used to categorize providers registered with Ts.ED's
|
|
51
|
+
* dependency injection container, enabling MonqueModule to discover and
|
|
52
|
+
* register workers automatically.
|
|
53
|
+
*
|
|
54
|
+
* Note: Using string constants with `as const` instead of enums per
|
|
55
|
+
* Constitution guidelines.
|
|
56
|
+
*/
|
|
57
|
+
const ProviderTypes = {
|
|
58
|
+
WORKER_CONTROLLER: "monque:worker-controller",
|
|
59
|
+
CRON: "monque:cron"
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/decorators/cron.ts
|
|
64
|
+
/**
|
|
65
|
+
* Method decorator that registers a method as a scheduled cron job.
|
|
66
|
+
*
|
|
67
|
+
* @param pattern - Cron expression (e.g., "* * * * *", "@daily")
|
|
68
|
+
* @param options - Optional cron configuration (name, timezone, etc.)
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* @WorkerController()
|
|
73
|
+
* class ReportWorkers {
|
|
74
|
+
* @Cron("@daily", { timezone: "UTC" })
|
|
75
|
+
* async generateDailyReport() {
|
|
76
|
+
* // ...
|
|
77
|
+
* }
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
function Cron(pattern, options) {
|
|
82
|
+
return (target, propertyKey, _descriptor) => {
|
|
83
|
+
const methodName = String(propertyKey);
|
|
84
|
+
const cronMetadata = {
|
|
85
|
+
pattern,
|
|
86
|
+
name: options?.name || methodName,
|
|
87
|
+
method: methodName,
|
|
88
|
+
opts: options || {}
|
|
89
|
+
};
|
|
90
|
+
const targetConstructor = target.constructor;
|
|
91
|
+
const store = Store.from(targetConstructor);
|
|
92
|
+
const existing = store.get(MONQUE) || {
|
|
93
|
+
type: "controller",
|
|
94
|
+
workers: [],
|
|
95
|
+
cronJobs: []
|
|
96
|
+
};
|
|
97
|
+
const cronJobs = [...existing.cronJobs || [], cronMetadata];
|
|
98
|
+
store.set(MONQUE, {
|
|
99
|
+
...existing,
|
|
100
|
+
cronJobs
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/decorators/worker.ts
|
|
107
|
+
/**
|
|
108
|
+
* @Worker method decorator
|
|
109
|
+
*
|
|
110
|
+
* Registers a method as a job handler. The method will be called when a job
|
|
111
|
+
* with the matching name is picked up for processing.
|
|
112
|
+
*
|
|
113
|
+
* @param name - Job name (combined with controller namespace if present).
|
|
114
|
+
* @param options - Worker configuration options.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* @WorkerController("notifications")
|
|
119
|
+
* export class NotificationWorkers {
|
|
120
|
+
* @Worker("push", { concurrency: 10 })
|
|
121
|
+
* async sendPush(job: Job<PushPayload>) {
|
|
122
|
+
* await pushService.send(job.data);
|
|
123
|
+
* }
|
|
124
|
+
* }
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
/**
|
|
128
|
+
* Method decorator that registers a method as a job handler.
|
|
129
|
+
*
|
|
130
|
+
* @param name - The job name (will be prefixed with controller namespace if present)
|
|
131
|
+
* @param options - Optional worker configuration (concurrency, replace, etc.)
|
|
132
|
+
*/
|
|
133
|
+
function Worker(name, options) {
|
|
134
|
+
return (target, propertyKey, _descriptor) => {
|
|
135
|
+
const workerMetadata = {
|
|
136
|
+
name,
|
|
137
|
+
method: String(propertyKey),
|
|
138
|
+
opts: options || {}
|
|
139
|
+
};
|
|
140
|
+
const targetConstructor = target.constructor;
|
|
141
|
+
const store = Store.from(targetConstructor);
|
|
142
|
+
const existing = store.get(MONQUE) || {
|
|
143
|
+
type: "controller",
|
|
144
|
+
workers: [],
|
|
145
|
+
cronJobs: []
|
|
146
|
+
};
|
|
147
|
+
const workers = [...existing.workers || [], workerMetadata];
|
|
148
|
+
store.set(MONQUE, {
|
|
149
|
+
...existing,
|
|
150
|
+
workers
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/decorators/worker-controller.ts
|
|
157
|
+
/**
|
|
158
|
+
* @WorkerController class decorator
|
|
159
|
+
*
|
|
160
|
+
* Marks a class as containing worker methods and registers it with the Ts.ED DI container.
|
|
161
|
+
* Workers in the class will have their job names prefixed with the namespace.
|
|
162
|
+
*
|
|
163
|
+
* @param namespace - Optional prefix for all job names in this controller.
|
|
164
|
+
* When set, job names become "{namespace}.{name}".
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* @WorkerController("email")
|
|
169
|
+
* export class EmailWorkers {
|
|
170
|
+
* @Worker("send") // Registered as "email.send"
|
|
171
|
+
* async send(job: Job<EmailPayload>) { }
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
/**
|
|
176
|
+
* Class decorator that registers a class as a worker controller.
|
|
177
|
+
*
|
|
178
|
+
* @param namespace - Optional namespace prefix for job names
|
|
179
|
+
*/
|
|
180
|
+
function WorkerController(namespace) {
|
|
181
|
+
return useDecorators(Injectable({ type: ProviderTypes.WORKER_CONTROLLER }), (target) => {
|
|
182
|
+
const store = Store.from(target);
|
|
183
|
+
const existing = store.get(MONQUE) || {};
|
|
184
|
+
const workerStore = {
|
|
185
|
+
type: "controller",
|
|
186
|
+
...namespace !== void 0 && { namespace },
|
|
187
|
+
workers: existing.workers || [],
|
|
188
|
+
cronJobs: existing.cronJobs || []
|
|
189
|
+
};
|
|
190
|
+
store.set(MONQUE, workerStore);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region \0@oxc-project+runtime@0.110.0/helpers/decorate.js
|
|
196
|
+
function __decorate(decorators, target, key, desc) {
|
|
197
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
198
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
199
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
200
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/services/monque-service.ts
|
|
205
|
+
let MonqueService = class MonqueService {
|
|
206
|
+
/**
|
|
207
|
+
* Internal Monque instance (set by MonqueModule)
|
|
208
|
+
* @internal
|
|
209
|
+
*/
|
|
210
|
+
_monque = null;
|
|
211
|
+
/**
|
|
212
|
+
* Set the internal Monque instance.
|
|
213
|
+
* Called by MonqueModule during initialization.
|
|
214
|
+
* @internal
|
|
215
|
+
*/
|
|
216
|
+
_setMonque(monque) {
|
|
217
|
+
this._monque = monque;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Access the underlying Monque instance.
|
|
221
|
+
* @throws Error if MonqueModule is not initialized
|
|
222
|
+
*/
|
|
223
|
+
get monque() {
|
|
224
|
+
if (!this._monque) throw new MonqueError("MonqueService is not initialized. Ensure MonqueModule is imported and enabled.");
|
|
225
|
+
return this._monque;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Enqueue a job for processing.
|
|
229
|
+
*
|
|
230
|
+
* @param name - Job type identifier (use full namespaced name, e.g., "email.send")
|
|
231
|
+
* @param data - Job payload
|
|
232
|
+
* @param options - Scheduling and deduplication options
|
|
233
|
+
* @returns The created or existing job document
|
|
234
|
+
*/
|
|
235
|
+
async enqueue(name, data, options) {
|
|
236
|
+
return this.monque.enqueue(name, data, options);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Enqueue a job for immediate processing.
|
|
240
|
+
*
|
|
241
|
+
* @param name - Job type identifier
|
|
242
|
+
* @param data - Job payload
|
|
243
|
+
* @returns The created job document
|
|
244
|
+
*/
|
|
245
|
+
async now(name, data) {
|
|
246
|
+
return this.monque.now(name, data);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Schedule a recurring job with a cron expression.
|
|
250
|
+
*
|
|
251
|
+
* @param cron - Cron expression (5-field standard or predefined like @daily)
|
|
252
|
+
* @param name - Job type identifier
|
|
253
|
+
* @param data - Job payload
|
|
254
|
+
* @param options - Scheduling options
|
|
255
|
+
* @returns The created job document
|
|
256
|
+
*/
|
|
257
|
+
async schedule(cron, name, data, options) {
|
|
258
|
+
return this.monque.schedule(cron, name, data, options);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Cancel a pending or scheduled job.
|
|
262
|
+
*
|
|
263
|
+
* @param jobId - The ID of the job to cancel
|
|
264
|
+
* @returns The cancelled job, or null if not found
|
|
265
|
+
*/
|
|
266
|
+
async cancelJob(jobId) {
|
|
267
|
+
return this.monque.cancelJob(jobId);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Retry a failed or cancelled job.
|
|
271
|
+
*
|
|
272
|
+
* @param jobId - The ID of the job to retry
|
|
273
|
+
* @returns The updated job, or null if not found
|
|
274
|
+
*/
|
|
275
|
+
async retryJob(jobId) {
|
|
276
|
+
return this.monque.retryJob(jobId);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Reschedule a pending job to run at a different time.
|
|
280
|
+
*
|
|
281
|
+
* @param jobId - The ID of the job to reschedule
|
|
282
|
+
* @param runAt - The new Date when the job should run
|
|
283
|
+
* @returns The updated job, or null if not found
|
|
284
|
+
*/
|
|
285
|
+
async rescheduleJob(jobId, runAt) {
|
|
286
|
+
return this.monque.rescheduleJob(jobId, runAt);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Permanently delete a job.
|
|
290
|
+
*
|
|
291
|
+
* @param jobId - The ID of the job to delete
|
|
292
|
+
* @returns true if deleted, false if job not found
|
|
293
|
+
*/
|
|
294
|
+
async deleteJob(jobId) {
|
|
295
|
+
return this.monque.deleteJob(jobId);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Cancel multiple jobs matching the given filter.
|
|
299
|
+
*
|
|
300
|
+
* @param filter - Selector for which jobs to cancel
|
|
301
|
+
* @returns Result with count of cancelled jobs
|
|
302
|
+
*/
|
|
303
|
+
async cancelJobs(filter) {
|
|
304
|
+
return this.monque.cancelJobs(filter);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Retry multiple jobs matching the given filter.
|
|
308
|
+
*
|
|
309
|
+
* @param filter - Selector for which jobs to retry
|
|
310
|
+
* @returns Result with count of retried jobs
|
|
311
|
+
*/
|
|
312
|
+
async retryJobs(filter) {
|
|
313
|
+
return this.monque.retryJobs(filter);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Delete multiple jobs matching the given filter.
|
|
317
|
+
*
|
|
318
|
+
* @param filter - Selector for which jobs to delete
|
|
319
|
+
* @returns Result with count of deleted jobs
|
|
320
|
+
*/
|
|
321
|
+
async deleteJobs(filter) {
|
|
322
|
+
return this.monque.deleteJobs(filter);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get a job by its ID.
|
|
326
|
+
*
|
|
327
|
+
* @param jobId - The job's ObjectId (as string or ObjectId)
|
|
328
|
+
* @returns The job document, or null if not found
|
|
329
|
+
* @throws MonqueError if jobId is an invalid hex string
|
|
330
|
+
*/
|
|
331
|
+
async getJob(jobId) {
|
|
332
|
+
let id;
|
|
333
|
+
if (typeof jobId === "string") {
|
|
334
|
+
if (!ObjectId.isValid(jobId)) throw new MonqueError(`Invalid job ID format: ${jobId}`);
|
|
335
|
+
id = ObjectId.createFromHexString(jobId);
|
|
336
|
+
} else id = jobId;
|
|
337
|
+
return this.monque.getJob(id);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Query jobs from the queue with optional filters.
|
|
341
|
+
*
|
|
342
|
+
* @param filter - Optional filter criteria (name, status, limit, skip)
|
|
343
|
+
* @returns Array of matching jobs
|
|
344
|
+
*/
|
|
345
|
+
async getJobs(filter) {
|
|
346
|
+
return this.monque.getJobs(filter);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get a paginated list of jobs using opaque cursors.
|
|
350
|
+
*
|
|
351
|
+
* @param options - Pagination options (cursor, limit, direction, filter)
|
|
352
|
+
* @returns Page of jobs with next/prev cursors
|
|
353
|
+
*/
|
|
354
|
+
async getJobsWithCursor(options) {
|
|
355
|
+
return this.monque.getJobsWithCursor(options);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Get aggregate statistics for the job queue.
|
|
359
|
+
*
|
|
360
|
+
* @param filter - Optional filter to scope statistics by job name
|
|
361
|
+
* @returns Queue statistics
|
|
362
|
+
*/
|
|
363
|
+
async getQueueStats(filter) {
|
|
364
|
+
return this.monque.getQueueStats(filter);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Check if the scheduler is healthy and running.
|
|
368
|
+
*
|
|
369
|
+
* @returns true if running and connected
|
|
370
|
+
*/
|
|
371
|
+
isHealthy() {
|
|
372
|
+
return this.monque.isHealthy();
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
MonqueService = __decorate([Injectable()], MonqueService);
|
|
376
|
+
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region src/utils/build-job-name.ts
|
|
379
|
+
/**
|
|
380
|
+
* Build the full job name by combining namespace and name.
|
|
381
|
+
*
|
|
382
|
+
* @param namespace - Optional namespace from @WorkerController
|
|
383
|
+
* @param name - Job name from @Worker or @Cron
|
|
384
|
+
* @returns Full job name (e.g., "email.send" or just "send")
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```typescript
|
|
388
|
+
* buildJobName("email", "send"); // "email.send"
|
|
389
|
+
* buildJobName(undefined, "send"); // "send"
|
|
390
|
+
* buildJobName("", "send"); // "send"
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
393
|
+
function buildJobName(namespace, name) {
|
|
394
|
+
return namespace ? `${namespace}.${name}` : name;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/utils/collect-worker-metadata.ts
|
|
399
|
+
/**
|
|
400
|
+
* Collect worker metadata utility
|
|
401
|
+
*
|
|
402
|
+
* Collects all worker metadata from a class decorated with @WorkerController.
|
|
403
|
+
* Used by MonqueModule to discover and register all workers.
|
|
404
|
+
*/
|
|
405
|
+
/**
|
|
406
|
+
* Collect all worker metadata from a class.
|
|
407
|
+
*
|
|
408
|
+
* @param target - The class constructor (decorated with @WorkerController)
|
|
409
|
+
* @returns Array of collected worker metadata ready for registration
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```typescript
|
|
413
|
+
* const metadata = collectWorkerMetadata(EmailWorkers);
|
|
414
|
+
* // Returns:
|
|
415
|
+
* // [
|
|
416
|
+
* // { fullName: "email.send", method: "sendEmail", opts: {}, isCron: false },
|
|
417
|
+
* // { fullName: "email.daily-digest", method: "sendDailyDigest", opts: {}, isCron: true, cronPattern: "0 9 * * *" }
|
|
418
|
+
* // ]
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
function collectWorkerMetadata(target) {
|
|
422
|
+
const workerStore = Store.from(target).get(MONQUE);
|
|
423
|
+
if (!workerStore) return [];
|
|
424
|
+
const results = [];
|
|
425
|
+
const namespace = workerStore.namespace;
|
|
426
|
+
for (const worker of workerStore.workers) results.push({
|
|
427
|
+
fullName: buildJobName(namespace, worker.name),
|
|
428
|
+
method: worker.method,
|
|
429
|
+
opts: worker.opts,
|
|
430
|
+
isCron: false
|
|
431
|
+
});
|
|
432
|
+
for (const cron of workerStore.cronJobs) results.push({
|
|
433
|
+
fullName: buildJobName(namespace, cron.name),
|
|
434
|
+
method: cron.method,
|
|
435
|
+
opts: cron.opts,
|
|
436
|
+
isCron: true,
|
|
437
|
+
cronPattern: cron.pattern
|
|
438
|
+
});
|
|
439
|
+
return results;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/utils/get-worker-token.ts
|
|
444
|
+
/**
|
|
445
|
+
* Generate a unique token for a worker controller.
|
|
446
|
+
*
|
|
447
|
+
* Used internally by Ts.ED DI to identify worker controller providers.
|
|
448
|
+
* The token is based on the class name for debugging purposes.
|
|
449
|
+
*
|
|
450
|
+
* @param target - The class constructor
|
|
451
|
+
* @returns A Symbol token unique to this worker controller
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* ```typescript
|
|
455
|
+
* @WorkerController("email")
|
|
456
|
+
* class EmailWorkers {}
|
|
457
|
+
*
|
|
458
|
+
* const token = getWorkerToken(EmailWorkers);
|
|
459
|
+
* // Symbol("monque:worker:EmailWorkers")
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
462
|
+
function getWorkerToken(target) {
|
|
463
|
+
const name = target.name?.trim();
|
|
464
|
+
if (!name) throw new Error("Worker class must have a non-empty name");
|
|
465
|
+
return Symbol.for(`monque:worker:${name}`);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region src/utils/guards.ts
|
|
470
|
+
/**
|
|
471
|
+
* Type guard to check if an object acts like a Mongoose Service.
|
|
472
|
+
*
|
|
473
|
+
* Checks if the object has a `get` method.
|
|
474
|
+
*
|
|
475
|
+
* @param value The value to check
|
|
476
|
+
*/
|
|
477
|
+
function isMongooseService(value) {
|
|
478
|
+
return typeof value === "object" && value !== null && "get" in value && typeof value.get === "function";
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Type guard to check if an object acts like a Mongoose Connection.
|
|
482
|
+
*
|
|
483
|
+
* Checks if the object has a `db` property.
|
|
484
|
+
*
|
|
485
|
+
* @param value The value to check
|
|
486
|
+
*/
|
|
487
|
+
function isMongooseConnection(value) {
|
|
488
|
+
return typeof value === "object" && value !== null && "db" in value && typeof value.db === "object" && value.db !== null && typeof value.db.collection === "function";
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
//#endregion
|
|
492
|
+
//#region src/utils/resolve-database.ts
|
|
493
|
+
/**
|
|
494
|
+
* Resolve the MongoDB database instance from the configuration.
|
|
495
|
+
*
|
|
496
|
+
* Supports three resolution strategies:
|
|
497
|
+
* 1. **Direct `db`**: Returns the provided Db instance directly
|
|
498
|
+
* 2. **Factory `dbFactory`**: Calls the factory function (supports async)
|
|
499
|
+
* 3. **DI Token `dbToken`**: Resolves the Db from the DI container
|
|
500
|
+
*
|
|
501
|
+
* @param config - The Monque configuration containing database settings
|
|
502
|
+
* @param injectorFn - Optional function to resolve DI tokens (required for dbToken strategy)
|
|
503
|
+
* @returns The resolved MongoDB Db instance
|
|
504
|
+
* @throws Error if no database strategy is provided or if DI resolution fails
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* ```typescript
|
|
508
|
+
* // Direct Db instance
|
|
509
|
+
* const db = await resolveDatabase({ db: mongoDb });
|
|
510
|
+
*
|
|
511
|
+
* // Factory function
|
|
512
|
+
* const db = await resolveDatabase({
|
|
513
|
+
* dbFactory: async () => {
|
|
514
|
+
* const client = await MongoClient.connect(uri);
|
|
515
|
+
* return client.db("myapp");
|
|
516
|
+
* }
|
|
517
|
+
* });
|
|
518
|
+
*
|
|
519
|
+
* // DI token
|
|
520
|
+
* const db = await resolveDatabase(
|
|
521
|
+
* { dbToken: "MONGODB_DATABASE" },
|
|
522
|
+
* (token) => injector.get(token)
|
|
523
|
+
* );
|
|
524
|
+
* ```
|
|
525
|
+
*/
|
|
526
|
+
async function resolveDatabase(config, injectorFn) {
|
|
527
|
+
if (config.db) return config.db;
|
|
528
|
+
if (config.dbFactory) return config.dbFactory();
|
|
529
|
+
if (config.dbToken) {
|
|
530
|
+
if (!injectorFn) throw new Error("MonqueTsedConfig.dbToken requires an injector function to resolve the database");
|
|
531
|
+
const resolved = injectorFn(config.dbToken);
|
|
532
|
+
if (!resolved) throw new Error(`Could not resolve database from token: ${String(config.dbToken)}. Make sure the provider is registered in the DI container.`);
|
|
533
|
+
if (isMongooseService(resolved)) {
|
|
534
|
+
const connectionId = config.mongooseConnectionId || "default";
|
|
535
|
+
const connection = resolved.get(connectionId);
|
|
536
|
+
if (!connection) throw new Error(`MongooseService resolved from token "${String(config.dbToken)}" returned no connection for ID "${connectionId}". Ensure the connection ID is correct and the connection is established.`);
|
|
537
|
+
if ("db" in connection && connection.db) return connection.db;
|
|
538
|
+
}
|
|
539
|
+
if (isMongooseConnection(resolved)) return resolved.db;
|
|
540
|
+
if (typeof resolved !== "object" || resolved === null || !("collection" in resolved)) throw new Error(`Resolved value from token "${String(config.dbToken)}" does not appear to be a valid MongoDB Db instance.`);
|
|
541
|
+
return resolved;
|
|
542
|
+
}
|
|
543
|
+
throw new Error("MonqueTsedConfig requires 'db', 'dbFactory', or 'dbToken' to be set");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region \0@oxc-project+runtime@0.110.0/helpers/decorateMetadata.js
|
|
548
|
+
function __decorateMetadata(k, v) {
|
|
549
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region \0@oxc-project+runtime@0.110.0/helpers/decorateParam.js
|
|
554
|
+
function __decorateParam(paramIndex, decorator) {
|
|
555
|
+
return function(target, key) {
|
|
556
|
+
decorator(target, key, paramIndex);
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
//#endregion
|
|
561
|
+
//#region src/monque-module.ts
|
|
562
|
+
/**
|
|
563
|
+
* MonqueModule - Main Integration Module
|
|
564
|
+
*
|
|
565
|
+
* Orchestrates the integration between Monque and Ts.ED.
|
|
566
|
+
* Handles lifecycle hooks, configuration resolution, and worker registration.
|
|
567
|
+
*/
|
|
568
|
+
var _ref, _ref2, _ref3, _ref4;
|
|
569
|
+
let MonqueModule = class MonqueModule {
|
|
570
|
+
injector;
|
|
571
|
+
monqueService;
|
|
572
|
+
logger;
|
|
573
|
+
monqueConfig;
|
|
574
|
+
monque = null;
|
|
575
|
+
constructor(injector, monqueService, logger, configuration) {
|
|
576
|
+
this.injector = injector;
|
|
577
|
+
this.monqueService = monqueService;
|
|
578
|
+
this.logger = logger;
|
|
579
|
+
this.monqueConfig = configuration.get("monque") || {};
|
|
580
|
+
}
|
|
581
|
+
async $onInit() {
|
|
582
|
+
const config = this.monqueConfig;
|
|
583
|
+
if (config?.enabled === false) {
|
|
584
|
+
this.logger.info("Monque integration is disabled");
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
validateDatabaseConfig(config);
|
|
588
|
+
try {
|
|
589
|
+
const db = await resolveDatabase(config, (token) => this.injector.get(token));
|
|
590
|
+
const { db: _db, ...restConfig } = config;
|
|
591
|
+
this.monque = new Monque(db, restConfig);
|
|
592
|
+
this.monqueService._setMonque(this.monque);
|
|
593
|
+
this.logger.info("Monque: Connecting to MongoDB...");
|
|
594
|
+
await this.monque.initialize();
|
|
595
|
+
await this.registerWorkers();
|
|
596
|
+
await this.monque.start();
|
|
597
|
+
this.logger.info("Monque: Started successfully");
|
|
598
|
+
} catch (error) {
|
|
599
|
+
this.logger.error({
|
|
600
|
+
event: "MONQUE_INIT_ERROR",
|
|
601
|
+
message: "Failed to initialize Monque",
|
|
602
|
+
error
|
|
603
|
+
});
|
|
604
|
+
throw error;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
async $onDestroy() {
|
|
608
|
+
if (this.monque) {
|
|
609
|
+
this.logger.info("Monque: Stopping...");
|
|
610
|
+
await this.monque.stop();
|
|
611
|
+
this.logger.info("Monque: Stopped");
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Discover and register all workers from @WorkerController providers
|
|
616
|
+
*/
|
|
617
|
+
async registerWorkers() {
|
|
618
|
+
if (!this.monque) throw new Error("Monque instance not initialized");
|
|
619
|
+
const monque = this.monque;
|
|
620
|
+
const workerControllers = this.injector.getProviders(ProviderTypes.WORKER_CONTROLLER);
|
|
621
|
+
const registeredJobs = /* @__PURE__ */ new Set();
|
|
622
|
+
this.logger.info(`Monque: Found ${workerControllers.length} worker controllers`);
|
|
623
|
+
for (const provider of workerControllers) {
|
|
624
|
+
const useClass = provider.useClass;
|
|
625
|
+
const workers = collectWorkerMetadata(useClass);
|
|
626
|
+
const instance = this.injector.get(provider.token);
|
|
627
|
+
if (!instance && provider.scope !== ProviderScope.REQUEST) {
|
|
628
|
+
this.logger.warn(`Monque: Could not resolve instance for controller ${provider.name}. Skipping.`);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
for (const worker of workers) {
|
|
632
|
+
const { fullName, method, opts, isCron, cronPattern } = worker;
|
|
633
|
+
if (registeredJobs.has(fullName)) throw new Error(`Monque: Duplicate job registration detected. Job "${fullName}" is already registered.`);
|
|
634
|
+
registeredJobs.add(fullName);
|
|
635
|
+
const handler = async (job) => {
|
|
636
|
+
const $ctx = new DIContext({
|
|
637
|
+
injector: this.injector,
|
|
638
|
+
id: job._id?.toString() || "unknown"
|
|
639
|
+
});
|
|
640
|
+
$ctx.set("MONQUE_JOB", job);
|
|
641
|
+
$ctx.container.set(DIContext, $ctx);
|
|
642
|
+
await runInContext($ctx, async () => {
|
|
643
|
+
try {
|
|
644
|
+
let targetInstance = instance;
|
|
645
|
+
if (provider.scope === ProviderScope.REQUEST || !targetInstance) targetInstance = await this.injector.invoke(provider.token, { locals: $ctx.container });
|
|
646
|
+
const typedInstance = targetInstance;
|
|
647
|
+
if (typedInstance && typeof typedInstance[method] === "function") await typedInstance[method](job);
|
|
648
|
+
} catch (error) {
|
|
649
|
+
this.logger.error({
|
|
650
|
+
event: "MONQUE_JOB_ERROR",
|
|
651
|
+
jobName: fullName,
|
|
652
|
+
jobId: job._id,
|
|
653
|
+
message: `Error processing job ${fullName}`,
|
|
654
|
+
error
|
|
655
|
+
});
|
|
656
|
+
throw error;
|
|
657
|
+
} finally {
|
|
658
|
+
await $ctx.destroy();
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
};
|
|
662
|
+
if (isCron && cronPattern) {
|
|
663
|
+
this.logger.debug(`Monque: Registering cron job "${fullName}" (${cronPattern})`);
|
|
664
|
+
monque.register(fullName, handler, opts);
|
|
665
|
+
await monque.schedule(cronPattern, fullName, {}, opts);
|
|
666
|
+
} else {
|
|
667
|
+
this.logger.debug(`Monque: Registering worker "${fullName}"`);
|
|
668
|
+
monque.register(fullName, handler, opts);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
this.logger.info(`Monque: Registered ${registeredJobs.size} jobs`);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
MonqueModule = __decorate([
|
|
676
|
+
Module({ imports: [MonqueService] }),
|
|
677
|
+
__decorateParam(0, Inject(InjectorService)),
|
|
678
|
+
__decorateParam(1, Inject(MonqueService)),
|
|
679
|
+
__decorateParam(2, Inject(LOGGER)),
|
|
680
|
+
__decorateParam(3, Inject(Configuration)),
|
|
681
|
+
__decorateMetadata("design:paramtypes", [
|
|
682
|
+
typeof (_ref = typeof InjectorService !== "undefined" && InjectorService) === "function" ? _ref : Object,
|
|
683
|
+
typeof (_ref2 = typeof MonqueService !== "undefined" && MonqueService) === "function" ? _ref2 : Object,
|
|
684
|
+
typeof (_ref3 = typeof LOGGER !== "undefined" && LOGGER) === "function" ? _ref3 : Object,
|
|
685
|
+
typeof (_ref4 = typeof Configuration !== "undefined" && Configuration) === "function" ? _ref4 : Object
|
|
686
|
+
])
|
|
687
|
+
], MonqueModule);
|
|
688
|
+
|
|
689
|
+
//#endregion
|
|
690
|
+
export { Cron, MONQUE, MonqueModule, MonqueService, ProviderTypes, Worker, WorkerController, buildJobName, collectWorkerMetadata, getWorkerToken, resolveDatabase, validateDatabaseConfig };
|
|
691
|
+
//# sourceMappingURL=index.mjs.map
|