webspresso 0.0.90 → 0.0.93
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 +98 -2509
- package/bin/commands/admin-password.js +8 -2
- package/bin/commands/api.js +7 -1
- package/bin/commands/favicon-generate.js +6 -4
- package/bin/commands/mcp.js +162 -0
- package/bin/commands/new.js +7 -1
- package/bin/commands/page.js +7 -1
- package/bin/commands/seed.js +7 -1
- package/bin/commands/service.js +199 -0
- package/bin/commands/skill.js +7 -1
- package/bin/webspresso.js +4 -0
- package/core/auth/middleware.js +9 -0
- package/core/compileSchema.js +1 -5
- package/core/compression/compressible.js +63 -0
- package/core/compression/compression-stream.js +59 -0
- package/core/compression/index.js +25 -0
- package/core/compression/middleware.js +310 -0
- package/core/compression/negotiate.js +165 -0
- package/core/errors/base.js +31 -0
- package/core/errors/domain.js +193 -0
- package/core/errors/http.js +133 -0
- package/core/errors/index.js +77 -0
- package/core/errors/middleware.js +162 -0
- package/core/errors/normalize.js +171 -0
- package/core/kernel/app.js +2 -1
- package/core/kernel/index.js +5 -1
- package/core/orm/cache/fingerprint.js +23 -5
- package/core/orm/complexity.js +120 -0
- package/core/orm/eager-loader.js +54 -9
- package/core/orm/events.js +64 -14
- package/core/orm/index.js +83 -12
- package/core/orm/model.js +13 -2
- package/core/orm/query-builder.js +30 -4
- package/core/orm/repository.js +57 -25
- package/core/orm/transaction.js +39 -5
- package/core/orm/utils.js +6 -3
- package/core/queue/adapters/database.js +317 -0
- package/core/queue/adapters/memory.js +176 -0
- package/core/queue/adapters/redis.js +357 -0
- package/core/queue/index.js +21 -0
- package/core/queue/job.js +103 -0
- package/core/queue/manager.js +413 -0
- package/core/realtime/adapters/redis.js +431 -0
- package/core/realtime/adapters/socket-io.js +203 -0
- package/core/realtime/adapters/sse.js +210 -0
- package/core/realtime/adapters/websocket.js +337 -0
- package/core/realtime/auth-manager.js +140 -0
- package/core/realtime/core.js +359 -0
- package/core/realtime/emitter.js +98 -0
- package/core/realtime/errors.js +71 -0
- package/core/realtime/index.js +79 -0
- package/core/realtime/reconnect-manager.js +118 -0
- package/core/realtime/registry.js +243 -0
- package/core/shutdown/http-adapter.js +134 -0
- package/core/shutdown/index.js +12 -0
- package/core/shutdown/shutdown-manager.js +312 -0
- package/core/ssr/index.js +13 -0
- package/core/ssr/stream.js +174 -0
- package/core/validation/index.js +70 -0
- package/index.d.ts +924 -53
- package/index.js +110 -1
- package/package.json +30 -2
- package/plugins/admin-panel/api.js +37 -4
- package/plugins/admin-panel/app.js +57 -17
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +10 -2
- package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +51 -27
- package/plugins/admin-panel/client/parts/07-model-list.js +9 -4
- package/plugins/admin-panel/client/parts/08-record-list.js +37 -26
- package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
- package/plugins/admin-panel/core/admin-module.js +2 -2
- package/plugins/admin-panel/core/api-extensions.js +149 -0
- package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
- package/plugins/admin-panel/index.js +155 -23
- package/plugins/admin-panel/modules/bulk-actions.js +9 -2
- package/plugins/admin-panel/modules/custom-pages.js +144 -45
- package/plugins/admin-panel/modules/dashboard.js +8 -1
- package/plugins/admin-panel/modules/menu.js +85 -0
- package/plugins/admin-panel/modules/user-management.js +5 -2
- package/plugins/admin-panel/vendor/mithril.min.js +1 -0
- package/plugins/admin-panel/vendor/quill.min.js +7 -0
- package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
- package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
- package/plugins/basic-auth/index.js +293 -0
- package/plugins/data-exchange/export-xlsx.js +28 -2
- package/plugins/data-exchange/import.js +55 -16
- package/plugins/data-exchange/index.js +19 -0
- package/plugins/data-exchange/parse-table.js +14 -5
- package/plugins/data-exchange/record-selection.js +2 -0
- package/plugins/email/index.js +14 -1
- package/plugins/index.js +33 -0
- package/plugins/mcp/auth.js +104 -0
- package/plugins/mcp/discovery.js +88 -0
- package/plugins/mcp/index.js +208 -0
- package/plugins/mcp/prompts.js +149 -0
- package/plugins/mcp/providers/orm.js +325 -0
- package/plugins/mcp/providers/services.js +190 -0
- package/plugins/mcp/providers/system.js +87 -0
- package/plugins/mcp/server.js +472 -0
- package/plugins/mcp/transports/sse.js +139 -0
- package/plugins/mcp/transports/stdio.js +108 -0
- package/plugins/queue/index.js +108 -0
- package/plugins/realtime/index.js +150 -0
- package/plugins/recaptcha/index.js +11 -0
- package/plugins/redirect/index.js +9 -1
- package/plugins/rest-resources/index.js +280 -11
- package/plugins/upload/index.js +16 -0
- package/src/app-context.js +46 -6
- package/src/file-router.js +366 -130
- package/src/helpers.js +20 -2
- package/src/njk-frontmatter.js +24 -14
- package/src/plugin-manager.js +124 -4
- package/src/server.js +332 -54
- package/src/services/builtins/auth.js +273 -0
- package/src/services/builtins/exchange.js +229 -0
- package/src/services/builtins/mail.js +134 -0
- package/src/services/builtins/media.js +196 -0
- package/src/services/builtins/system.js +221 -0
- package/src/services/discovery.js +141 -0
- package/src/services/executor.js +292 -0
- package/src/services/index.js +70 -0
- package/src/services/memoize.js +256 -0
- package/src/services/registry.js +290 -0
- package/src/services/validator.js +230 -0
- package/templates/skills/webspresso-usage/REFERENCE-framework.md +6 -4
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue Manager & Worker Dispatcher
|
|
3
|
+
* @module core/queue/manager
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const EventEmitter = require('events');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const { Job } = require('./job');
|
|
12
|
+
const { MemoryQueueAdapter } = require('./adapters/memory');
|
|
13
|
+
|
|
14
|
+
class QueueManager extends EventEmitter {
|
|
15
|
+
/**
|
|
16
|
+
* @param {Object} [options]
|
|
17
|
+
* @param {any} [options.adapter] - Queue adapter instance (defaults to MemoryQueueAdapter)
|
|
18
|
+
* @param {number} [options.concurrency=2] - Maximum concurrent running jobs
|
|
19
|
+
* @param {number} [options.pollInterval=500] - Polling interval in ms when idle
|
|
20
|
+
* @param {string} [options.jobsDir] - Directory to auto-discover job handlers
|
|
21
|
+
* @param {boolean} [options.autoStart=true] - Start worker loop immediately
|
|
22
|
+
*/
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
super();
|
|
25
|
+
|
|
26
|
+
this.adapter = options.adapter || new MemoryQueueAdapter();
|
|
27
|
+
this.concurrency = Math.max(1, options.concurrency || 2);
|
|
28
|
+
this.pollInterval = Math.max(50, options.pollInterval || 500);
|
|
29
|
+
this.maxPollInterval = Math.max(this.pollInterval, options.maxPollInterval || (this.pollInterval * 5));
|
|
30
|
+
this._currentPollInterval = this.pollInterval;
|
|
31
|
+
this.jobsDir = options.jobsDir || null;
|
|
32
|
+
|
|
33
|
+
/** @type {Map<string, { handler: Function, options: Object }>} */
|
|
34
|
+
this.handlers = new Map();
|
|
35
|
+
|
|
36
|
+
/** @type {Map<string|number, Job>} */
|
|
37
|
+
this.activeJobs = new Map();
|
|
38
|
+
|
|
39
|
+
this.runningCount = 0;
|
|
40
|
+
this.isPaused = false;
|
|
41
|
+
this.isDraining = false;
|
|
42
|
+
this.isRunning = false;
|
|
43
|
+
this._timer = null;
|
|
44
|
+
|
|
45
|
+
if (this.jobsDir && fs.existsSync(this.jobsDir)) {
|
|
46
|
+
this.loadJobsFromDirectory(this.jobsDir);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (options.autoStart !== false) {
|
|
50
|
+
this.start();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Register a job processor handler
|
|
56
|
+
* @param {string} name - Job name (e.g. 'email.send')
|
|
57
|
+
* @param {Function} handler - Async worker function (job, ctx) => Promise<any>
|
|
58
|
+
* @param {Object} [options]
|
|
59
|
+
* @param {number} [options.attempts=3]
|
|
60
|
+
* @param {number|Object} [options.backoff]
|
|
61
|
+
* @param {number} [options.timeout=30000]
|
|
62
|
+
* @param {number} [options.priority=10]
|
|
63
|
+
*/
|
|
64
|
+
define(name, handler, options = {}) {
|
|
65
|
+
if (typeof name !== 'string' || !name) {
|
|
66
|
+
throw new Error('Job name must be a non-empty string');
|
|
67
|
+
}
|
|
68
|
+
if (typeof handler !== 'function') {
|
|
69
|
+
throw new Error(`Job handler for '${name}' must be a function`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.handlers.set(name, {
|
|
73
|
+
handler,
|
|
74
|
+
options: {
|
|
75
|
+
attempts: 3,
|
|
76
|
+
timeout: 30000,
|
|
77
|
+
priority: 10,
|
|
78
|
+
...options,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if a job handler is defined
|
|
87
|
+
* @param {string} name
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
has(name) {
|
|
91
|
+
return this.handlers.has(name);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Dispatch a job into the queue
|
|
96
|
+
* @param {string} name - Registered job name
|
|
97
|
+
* @param {any} [data={}] - Job payload
|
|
98
|
+
* @param {Object} [options] - Dispatch options (delay, priority, attempts, timeout, backoff)
|
|
99
|
+
* @returns {Promise<Job>}
|
|
100
|
+
*/
|
|
101
|
+
async dispatch(name, data = {}, options = {}) {
|
|
102
|
+
const registered = this.handlers.get(name);
|
|
103
|
+
const defaults = registered ? registered.options : {};
|
|
104
|
+
|
|
105
|
+
let runAt = new Date();
|
|
106
|
+
if (options.delay) {
|
|
107
|
+
if (typeof options.delay === 'number') {
|
|
108
|
+
runAt = new Date(Date.now() + options.delay);
|
|
109
|
+
} else if (options.delay instanceof Date) {
|
|
110
|
+
runAt = options.delay;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const job = new Job({
|
|
115
|
+
name,
|
|
116
|
+
data,
|
|
117
|
+
runAt,
|
|
118
|
+
priority: options.priority !== undefined ? options.priority : defaults.priority,
|
|
119
|
+
maxAttempts: options.attempts !== undefined ? options.attempts : defaults.attempts,
|
|
120
|
+
timeout: options.timeout !== undefined ? options.timeout : defaults.timeout,
|
|
121
|
+
backoff: options.backoff !== undefined ? options.backoff : defaults.backoff,
|
|
122
|
+
onProgress: (j, percent, msg) => {
|
|
123
|
+
this.emit('job:progress', j, percent, msg);
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const enqueued = await this.adapter.enqueue(job);
|
|
128
|
+
this.emit('job:queued', enqueued);
|
|
129
|
+
|
|
130
|
+
// Reset polling backoff and trigger immediate poll if capacity is available
|
|
131
|
+
this._currentPollInterval = this.pollInterval;
|
|
132
|
+
if (this.isRunning && !this.isPaused && this.runningCount < this.concurrency) {
|
|
133
|
+
setImmediate(() => this._tick());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return enqueued;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Start processing jobs
|
|
141
|
+
*/
|
|
142
|
+
start() {
|
|
143
|
+
if (this.isRunning) return this;
|
|
144
|
+
this.isRunning = true;
|
|
145
|
+
this.isPaused = false;
|
|
146
|
+
this._currentPollInterval = this.pollInterval;
|
|
147
|
+
this._scheduleNextTick(0);
|
|
148
|
+
this.emit('start');
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Pause processing (running jobs continue until finished)
|
|
154
|
+
*/
|
|
155
|
+
pause() {
|
|
156
|
+
this.isPaused = true;
|
|
157
|
+
if (this._timer) {
|
|
158
|
+
clearTimeout(this._timer);
|
|
159
|
+
this._timer = null;
|
|
160
|
+
}
|
|
161
|
+
this.emit('pause');
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Resume processing
|
|
167
|
+
*/
|
|
168
|
+
resume() {
|
|
169
|
+
if (!this.isRunning) return this.start();
|
|
170
|
+
this.isPaused = false;
|
|
171
|
+
this._currentPollInterval = this.pollInterval;
|
|
172
|
+
this._scheduleNextTick(0);
|
|
173
|
+
this.emit('resume');
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Gracefully drain all active jobs before stopping
|
|
179
|
+
* @param {number} [timeoutMs=10000]
|
|
180
|
+
* @returns {Promise<void>}
|
|
181
|
+
*/
|
|
182
|
+
async drain(timeoutMs = 10000) {
|
|
183
|
+
this.isDraining = true;
|
|
184
|
+
this.pause();
|
|
185
|
+
|
|
186
|
+
if (this.runningCount === 0) {
|
|
187
|
+
this.isDraining = false;
|
|
188
|
+
this.isRunning = false;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const start = Date.now();
|
|
193
|
+
while (this.runningCount > 0) {
|
|
194
|
+
if (Date.now() - start > timeoutMs) {
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
await new Promise((res) => setTimeout(res, 50));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
this.isDraining = false;
|
|
201
|
+
this.isRunning = false;
|
|
202
|
+
this.emit('drain');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Stop worker loop
|
|
207
|
+
*/
|
|
208
|
+
async stop() {
|
|
209
|
+
this.pause();
|
|
210
|
+
this.isRunning = false;
|
|
211
|
+
this.emit('stop');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get queue statistics
|
|
216
|
+
*/
|
|
217
|
+
async getStats() {
|
|
218
|
+
const stats = await this.adapter.getStats();
|
|
219
|
+
return {
|
|
220
|
+
...stats,
|
|
221
|
+
activeWorkers: this.runningCount,
|
|
222
|
+
concurrency: this.concurrency,
|
|
223
|
+
registeredJobs: Array.from(this.handlers.keys()),
|
|
224
|
+
isPaused: this.isPaused,
|
|
225
|
+
isRunning: this.isRunning,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get job by ID
|
|
231
|
+
* @param {string|number} jobId
|
|
232
|
+
*/
|
|
233
|
+
async getJob(jobId) {
|
|
234
|
+
return this.adapter.getJob(jobId);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Auto-discover and register job files from directory recursively
|
|
239
|
+
* @param {string} dirPath
|
|
240
|
+
*/
|
|
241
|
+
loadJobsFromDirectory(dirPath) {
|
|
242
|
+
if (!fs.existsSync(dirPath)) return;
|
|
243
|
+
|
|
244
|
+
const readRecursive = (curDir, basePrefix = '') => {
|
|
245
|
+
const entries = fs.readdirSync(curDir, { withFileTypes: true });
|
|
246
|
+
for (const entry of entries) {
|
|
247
|
+
const fullPath = path.join(curDir, entry.name);
|
|
248
|
+
if (entry.isDirectory()) {
|
|
249
|
+
readRecursive(fullPath, basePrefix ? `${basePrefix}.${entry.name}` : entry.name);
|
|
250
|
+
} else if (entry.isFile() && (entry.name.endsWith('.js') || entry.name.endsWith('.cjs'))) {
|
|
251
|
+
const nameWithoutExt = entry.name.replace(/\.(c)?js$/, '');
|
|
252
|
+
const jobName = basePrefix ? `${basePrefix}.${nameWithoutExt}` : nameWithoutExt;
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const mod = require(fullPath);
|
|
256
|
+
const handler = typeof mod === 'function' ? mod : (mod.handler || mod.default);
|
|
257
|
+
const options = mod.options || {};
|
|
258
|
+
|
|
259
|
+
if (typeof handler === 'function') {
|
|
260
|
+
this.define(jobName, handler, options);
|
|
261
|
+
}
|
|
262
|
+
} catch (err) {
|
|
263
|
+
console.error(`[QueueManager] Failed to load job from ${fullPath}:`, err.message);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
readRecursive(dirPath);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Schedule next tick
|
|
274
|
+
* @private
|
|
275
|
+
*/
|
|
276
|
+
_scheduleNextTick(delay = this._currentPollInterval) {
|
|
277
|
+
if (!this.isRunning || this.isPaused || this.isDraining) return;
|
|
278
|
+
if (this._timer) clearTimeout(this._timer);
|
|
279
|
+
this._timer = setTimeout(() => this._tick(), delay);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Process loop tick
|
|
284
|
+
* @private
|
|
285
|
+
*/
|
|
286
|
+
async _tick() {
|
|
287
|
+
if (!this.isRunning || this.isPaused || this.isDraining) return;
|
|
288
|
+
|
|
289
|
+
const availableSlots = this.concurrency - this.runningCount;
|
|
290
|
+
if (availableSlots <= 0) return;
|
|
291
|
+
|
|
292
|
+
const registeredNames = Array.from(this.handlers.keys());
|
|
293
|
+
if (registeredNames.length === 0) {
|
|
294
|
+
this._scheduleNextTick(this._currentPollInterval);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let jobs = [];
|
|
299
|
+
try {
|
|
300
|
+
if (typeof this.adapter.dequeueMany === 'function' && availableSlots > 1) {
|
|
301
|
+
jobs = await this.adapter.dequeueMany(registeredNames, availableSlots);
|
|
302
|
+
} else {
|
|
303
|
+
const single = await this.adapter.dequeue(registeredNames);
|
|
304
|
+
if (single) jobs = [single];
|
|
305
|
+
}
|
|
306
|
+
} catch (err) {
|
|
307
|
+
this.emit('error', err);
|
|
308
|
+
this._scheduleNextTick(this._currentPollInterval);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!jobs || jobs.length === 0) {
|
|
313
|
+
// No jobs ready -> back off polling interval
|
|
314
|
+
this._currentPollInterval = Math.min(
|
|
315
|
+
this.maxPollInterval,
|
|
316
|
+
Math.round(this._currentPollInterval * 1.5)
|
|
317
|
+
);
|
|
318
|
+
this._scheduleNextTick(this._currentPollInterval);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Found job(s) -> reset polling interval
|
|
323
|
+
this._currentPollInterval = this.pollInterval;
|
|
324
|
+
|
|
325
|
+
for (const job of jobs) {
|
|
326
|
+
if (this.isPaused || this.isDraining) break;
|
|
327
|
+
|
|
328
|
+
this.runningCount++;
|
|
329
|
+
this.activeJobs.set(job.id, job);
|
|
330
|
+
|
|
331
|
+
// Attach progress handler
|
|
332
|
+
job._onProgress = (j, percent, msg) => {
|
|
333
|
+
this.emit('job:progress', j, percent, msg);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
this._executeJob(job).finally(() => {
|
|
337
|
+
this.runningCount--;
|
|
338
|
+
this.activeJobs.delete(job.id);
|
|
339
|
+
if (this.isRunning && !this.isPaused) {
|
|
340
|
+
this._scheduleNextTick(0);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (this.runningCount < this.concurrency) {
|
|
346
|
+
this._scheduleNextTick(0);
|
|
347
|
+
} else {
|
|
348
|
+
this._scheduleNextTick(this.pollInterval);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Execute single job with timeout & retry handling
|
|
354
|
+
* @private
|
|
355
|
+
*/
|
|
356
|
+
async _executeJob(job) {
|
|
357
|
+
const registered = this.handlers.get(job.name);
|
|
358
|
+
if (!registered) {
|
|
359
|
+
const err = new Error(`No handler registered for job '${job.name}'`);
|
|
360
|
+
await this.adapter.fail(job.id, err);
|
|
361
|
+
this.emit('job:failed', job, err);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.emit('job:started', job);
|
|
366
|
+
|
|
367
|
+
const { handler } = registered;
|
|
368
|
+
const timeoutMs = job.timeout || 30000;
|
|
369
|
+
|
|
370
|
+
let timeoutTimer = null;
|
|
371
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
372
|
+
timeoutTimer = setTimeout(() => {
|
|
373
|
+
reject(new Error(`Job '${job.name}' (${job.id}) exceeded timeout of ${timeoutMs}ms`));
|
|
374
|
+
}, timeoutMs);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
const result = await Promise.race([
|
|
379
|
+
handler(job, { queue: this }),
|
|
380
|
+
timeoutPromise,
|
|
381
|
+
]);
|
|
382
|
+
clearTimeout(timeoutTimer);
|
|
383
|
+
|
|
384
|
+
const completedJob = await this.adapter.complete(job.id, result);
|
|
385
|
+
this.emit('job:completed', completedJob || job, result);
|
|
386
|
+
} catch (err) {
|
|
387
|
+
clearTimeout(timeoutTimer);
|
|
388
|
+
|
|
389
|
+
if (job.attempts < job.maxAttempts) {
|
|
390
|
+
const delayMs = job.getNextRetryDelay();
|
|
391
|
+
const retryingJob = await this.adapter.retry(job.id, delayMs, err);
|
|
392
|
+
this.emit('job:retrying', retryingJob || job, err, delayMs);
|
|
393
|
+
} else {
|
|
394
|
+
const failedJob = await this.adapter.fail(job.id, err);
|
|
395
|
+
this.emit('job:failed', failedJob || job, err);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Factory to create QueueManager
|
|
403
|
+
* @param {Object} [options]
|
|
404
|
+
* @returns {QueueManager}
|
|
405
|
+
*/
|
|
406
|
+
function createQueueManager(options = {}) {
|
|
407
|
+
return new QueueManager(options);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
module.exports = {
|
|
411
|
+
QueueManager,
|
|
412
|
+
createQueueManager,
|
|
413
|
+
};
|