@archlast/server 0.1.8 → 0.1.9
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 +104 -100
- package/dist/admin/auth.d.ts +24 -5
- package/dist/admin/auth.js +49 -25
- package/dist/admin/schema.d.ts +122 -32
- package/dist/admin/schema.js +131 -95
- package/dist/admin/seed.d.ts +1 -1
- package/dist/admin/seed.js +79 -47
- package/dist/auth/api-key-resolver.d.ts +1 -1
- package/dist/auth/api-key-resolver.js +7 -3
- package/dist/auth/archlast-auth-adapter.d.ts +2 -5
- package/dist/auth/archlast-auth-adapter.js +1 -1
- package/dist/auth/better-auth-adapter.d.ts.map +1 -1
- package/dist/auth/better-auth-adapter.js +41 -26
- package/dist/auth/better-auth-adapter.js.map +1 -1
- package/dist/auth/better-auth-admin.d.ts.map +1 -1
- package/dist/auth/better-auth-admin.js +1 -1
- package/dist/auth/better-auth-admin.js.map +1 -1
- package/dist/auth/better-auth-api-key-resolver.js +1 -1
- package/dist/auth/better-auth-api-key-resolver.js.map +1 -1
- package/dist/auth/better-auth-instance.d.ts +249 -301
- package/dist/auth/better-auth-instance.d.ts.map +1 -1
- package/dist/auth/better-auth-instance.js +11 -0
- package/dist/auth/better-auth-instance.js.map +1 -1
- package/dist/auth/better-auth-seed.d.ts +5 -2
- package/dist/auth/better-auth-seed.js +31 -22
- package/dist/auth/better-auth-session-adapter.d.ts.map +1 -1
- package/dist/auth/better-auth-session-adapter.js +14 -10
- package/dist/auth/better-auth-session-adapter.js.map +1 -1
- package/dist/auth/errors.d.ts.map +1 -1
- package/dist/auth/errors.js +11 -11
- package/dist/auth/errors.js.map +1 -1
- package/dist/auth/oauth-proxy.d.ts +5 -2
- package/dist/auth/oauth-proxy.js +23 -27
- package/dist/auth/resolver.d.ts.map +1 -1
- package/dist/auth/resolver.js.map +1 -1
- package/dist/auth/role-helpers.d.ts +1 -1
- package/dist/auth/role-helpers.d.ts.map +1 -1
- package/dist/auth/role-helpers.js.map +1 -1
- package/dist/auth/session-manager.d.ts +2 -5
- package/dist/auth/session-manager.js +16 -6
- package/dist/auth/system/better-auth-schema.d.ts.map +1 -1
- package/dist/auth/system/better-auth-schema.js +6 -23
- package/dist/auth/system/better-auth-schema.js.map +1 -1
- package/dist/cache/circuit-breaker.d.ts +81 -0
- package/dist/cache/circuit-breaker.d.ts.map +1 -0
- package/dist/cache/circuit-breaker.js +170 -0
- package/dist/cache/circuit-breaker.js.map +1 -0
- package/dist/cache/client.d.ts +6 -3
- package/dist/cache/client.d.ts.map +1 -1
- package/dist/cache/client.js +12 -53
- package/dist/cache/client.js.map +1 -1
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +5 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/invalidation-queue.d.ts +63 -0
- package/dist/cache/invalidation-queue.d.ts.map +1 -0
- package/dist/cache/invalidation-queue.js +196 -0
- package/dist/cache/invalidation-queue.js.map +1 -0
- package/dist/cache/layers.d.ts +14 -4
- package/dist/cache/layers.d.ts.map +1 -1
- package/dist/cache/layers.js +66 -72
- package/dist/cache/layers.js.map +1 -1
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +6 -41
- package/dist/cache/manager.js.map +1 -1
- package/dist/cache/protocol.d.ts +4 -39
- package/dist/cache/protocol.d.ts.map +1 -1
- package/dist/cache/protocol.js.map +1 -1
- package/dist/cache/redis-adapter.d.ts +103 -0
- package/dist/cache/redis-adapter.d.ts.map +1 -0
- package/dist/cache/redis-adapter.js +424 -0
- package/dist/cache/redis-adapter.js.map +1 -0
- package/dist/cache/run-sidecar.js +10 -1
- package/dist/cache/run-sidecar.js.map +1 -1
- package/dist/cache/sidecar-server.d.ts +51 -1
- package/dist/cache/sidecar-server.d.ts.map +1 -1
- package/dist/cache/sidecar-server.js +368 -22
- package/dist/cache/sidecar-server.js.map +1 -1
- package/dist/cache/store.d.ts +43 -0
- package/dist/cache/store.d.ts.map +1 -1
- package/dist/cache/store.js +69 -76
- package/dist/cache/store.js.map +1 -1
- package/dist/cache/strategies.d.ts +2 -9
- package/dist/cache/strategies.d.ts.map +1 -1
- package/dist/cache/types.d.ts +130 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +60 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/config/bullmq.d.ts +16 -0
- package/dist/config/bullmq.d.ts.map +1 -0
- package/dist/config/bullmq.js +103 -0
- package/dist/config/bullmq.js.map +1 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +80 -6
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +71 -6
- package/dist/config/schema.js.map +1 -1
- package/dist/config/service.d.ts +54 -4
- package/dist/config/service.d.ts.map +1 -1
- package/dist/config/service.js +56 -2
- package/dist/config/service.js.map +1 -1
- package/dist/controllers/admin/admin-tokens.controller.d.ts +131 -115
- package/dist/controllers/admin/admin-tokens.controller.js +117 -98
- package/dist/controllers/admin/api-keys.controller.d.ts +1 -1
- package/dist/controllers/admin/api-keys.controller.d.ts.map +1 -1
- package/dist/controllers/admin/api-keys.controller.js.map +1 -1
- package/dist/controllers/admin/app-users.controller.d.ts +274 -243
- package/dist/controllers/admin/app-users.controller.js +301 -257
- package/dist/controllers/admin/auth.controller.d.ts +260 -236
- package/dist/controllers/admin/auth.controller.js +197 -174
- package/dist/controllers/admin/backup.controller.d.ts.map +1 -1
- package/dist/controllers/admin/backup.controller.js.map +1 -1
- package/dist/controllers/admin/settings.controller.d.ts +1 -1
- package/dist/controllers/admin/storage-stats.controller.d.ts +63 -0
- package/dist/controllers/admin/storage-stats.controller.d.ts.map +1 -0
- package/dist/controllers/admin/storage-stats.controller.js +33 -0
- package/dist/controllers/admin/storage-stats.controller.js.map +1 -0
- package/dist/controllers/admin/tenants.controller.d.ts.map +1 -1
- package/dist/controllers/admin/tenants.controller.js.map +1 -1
- package/dist/controllers/admin/users.controller.d.ts +1 -1
- package/dist/controllers/admin/users.controller.d.ts.map +1 -1
- package/dist/controllers/admin/users.controller.js.map +1 -1
- package/dist/controllers/auth.controller.d.ts +289 -271
- package/dist/controllers/auth.controller.js +275 -226
- package/dist/controllers/crud-generator.controller.d.ts.map +1 -1
- package/dist/controllers/crud-generator.controller.js +127 -125
- package/dist/controllers/crud-generator.controller.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/introspection.controller.d.ts +642 -0
- package/dist/controllers/introspection.controller.d.ts.map +1 -1
- package/dist/controllers/introspection.controller.js +611 -0
- package/dist/controllers/introspection.controller.js.map +1 -1
- package/dist/controllers/invite.controller.d.ts +190 -170
- package/dist/controllers/invite.controller.js +183 -164
- package/dist/controllers/mfa.controller.d.ts +205 -183
- package/dist/controllers/mfa.controller.js +131 -111
- package/dist/controllers/otp.controller.d.ts +194 -171
- package/dist/controllers/otp.controller.js +192 -175
- package/dist/controllers/storage.controller.d.ts.map +1 -1
- package/dist/controllers/storage.controller.js.map +1 -1
- package/dist/controllers/system.controller.d.ts +5 -3
- package/dist/controllers/system.controller.d.ts.map +1 -1
- package/dist/controllers/system.controller.js +4 -2
- package/dist/controllers/system.controller.js.map +1 -1
- package/dist/controllers/tenant.controller.d.ts +258 -227
- package/dist/controllers/tenant.controller.js +224 -200
- package/dist/db/cachedclient.d.ts +6 -11
- package/dist/db/cachedclient.d.ts.map +1 -1
- package/dist/db/cachedclient.js +79 -43
- package/dist/db/cachedclient.js.map +1 -1
- package/dist/db/distributed-client.d.ts +79 -24
- package/dist/db/distributed-client.js +23 -24
- package/dist/db/factory.d.ts +3 -8
- package/dist/db/factory.d.ts.map +1 -1
- package/dist/db/factory.js +3 -22
- package/dist/db/factory.js.map +1 -1
- package/dist/db/socket-client.d.ts +7 -0
- package/dist/db/socket-client.d.ts.map +1 -1
- package/dist/db/socket-client.js +140 -11
- package/dist/db/socket-client.js.map +1 -1
- package/dist/deployment/handler.d.ts +10 -2
- package/dist/deployment/handler.d.ts.map +1 -1
- package/dist/deployment/handler.js +70 -15
- package/dist/deployment/handler.js.map +1 -1
- package/dist/deployment/persistence.d.ts.map +1 -1
- package/dist/deployment/persistence.js +6 -1
- package/dist/deployment/persistence.js.map +1 -1
- package/dist/docker/compose.d.ts.map +1 -1
- package/dist/docker/compose.js +76 -0
- package/dist/docker/compose.js.map +1 -1
- package/dist/engine/runner.d.ts.map +1 -1
- package/dist/engine/runner.js +0 -43
- package/dist/engine/runner.js.map +1 -1
- package/dist/functions/built-in/auth-apikey.d.ts.map +1 -1
- package/dist/functions/built-in/auth-apikey.js.map +1 -1
- package/dist/functions/built-in/system-cache.d.ts.map +1 -1
- package/dist/functions/built-in/system-cache.js +6 -31
- package/dist/functions/built-in/system-cache.js.map +1 -1
- package/dist/functions/built-in/system-data.d.ts.map +1 -1
- package/dist/functions/built-in/system-data.js +4 -2
- package/dist/functions/built-in/system-data.js.map +1 -1
- package/dist/functions/definition.d.ts.map +1 -1
- package/dist/functions/definition.js +6 -2
- package/dist/functions/definition.js.map +1 -1
- package/dist/http/routes/metrics.d.ts +42 -0
- package/dist/http/routes/metrics.d.ts.map +1 -0
- package/dist/http/routes/metrics.js +29 -0
- package/dist/http/routes/metrics.js.map +1 -0
- package/dist/http/server.d.ts +1 -0
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +41 -3
- package/dist/http/server.js.map +1 -1
- package/dist/ipc/socket-bridge.d.ts +1 -0
- package/dist/ipc/socket-bridge.d.ts.map +1 -1
- package/dist/ipc/socket-bridge.js +5 -1
- package/dist/ipc/socket-bridge.js.map +1 -1
- package/dist/jobs/bullmq-adapter.d.ts +154 -0
- package/dist/jobs/bullmq-adapter.d.ts.map +1 -0
- package/dist/jobs/bullmq-adapter.js +688 -0
- package/dist/jobs/bullmq-adapter.js.map +1 -0
- package/dist/jobs/bullmq-circuit-breaker.d.ts +133 -0
- package/dist/jobs/bullmq-circuit-breaker.d.ts.map +1 -0
- package/dist/jobs/bullmq-circuit-breaker.js +323 -0
- package/dist/jobs/bullmq-circuit-breaker.js.map +1 -0
- package/dist/jobs/bullmq-dlq-manager.d.ts +155 -0
- package/dist/jobs/bullmq-dlq-manager.d.ts.map +1 -0
- package/dist/jobs/bullmq-dlq-manager.js +325 -0
- package/dist/jobs/bullmq-dlq-manager.js.map +1 -0
- package/dist/jobs/bullmq-metrics.d.ts +104 -0
- package/dist/jobs/bullmq-metrics.d.ts.map +1 -0
- package/dist/jobs/bullmq-metrics.js +323 -0
- package/dist/jobs/bullmq-metrics.js.map +1 -0
- package/dist/jobs/bullmq-priority-service.d.ts +173 -0
- package/dist/jobs/bullmq-priority-service.d.ts.map +1 -0
- package/dist/jobs/bullmq-priority-service.js +390 -0
- package/dist/jobs/bullmq-priority-service.js.map +1 -0
- package/dist/jobs/bullmq-scheduler.d.ts +111 -0
- package/dist/jobs/bullmq-scheduler.d.ts.map +1 -0
- package/dist/jobs/bullmq-scheduler.js +300 -0
- package/dist/jobs/bullmq-scheduler.js.map +1 -0
- package/dist/jobs/bullmq-worker.d.ts +155 -0
- package/dist/jobs/bullmq-worker.d.ts.map +1 -0
- package/dist/jobs/bullmq-worker.js +651 -0
- package/dist/jobs/bullmq-worker.js.map +1 -0
- package/dist/jobs/circuit-breaker.d.ts +120 -0
- package/dist/jobs/circuit-breaker.d.ts.map +1 -0
- package/dist/jobs/circuit-breaker.js +262 -0
- package/dist/jobs/circuit-breaker.js.map +1 -0
- package/dist/jobs/index.d.ts +1 -1
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js.map +1 -1
- package/dist/jobs/queue.d.ts +120 -1
- package/dist/jobs/queue.d.ts.map +1 -1
- package/dist/jobs/queue.js +487 -9
- package/dist/jobs/queue.js.map +1 -1
- package/dist/jobs/redis-connection.d.ts +50 -0
- package/dist/jobs/redis-connection.d.ts.map +1 -0
- package/dist/jobs/redis-connection.js +123 -0
- package/dist/jobs/redis-connection.js.map +1 -0
- package/dist/jobs/run-scheduler.js +163 -10
- package/dist/jobs/run-scheduler.js.map +1 -1
- package/dist/jobs/run-worker.js +101 -9
- package/dist/jobs/run-worker.js.map +1 -1
- package/dist/jobs/worker-thread.d.ts +6 -0
- package/dist/jobs/worker-thread.d.ts.map +1 -1
- package/dist/jobs/worker-thread.js +37 -8
- package/dist/jobs/worker-thread.js.map +1 -1
- package/dist/jobs/worker.d.ts +33 -0
- package/dist/jobs/worker.d.ts.map +1 -1
- package/dist/jobs/worker.js +358 -115
- package/dist/jobs/worker.js.map +1 -1
- package/dist/linq/async-enumerable.d.ts.map +1 -1
- package/dist/linq/async-enumerable.js.map +1 -1
- package/dist/linq/enumerable.d.ts.map +1 -1
- package/dist/linq/enumerable.js +10 -10
- package/dist/linq/enumerable.js.map +1 -1
- package/dist/metrics/collector.d.ts +26 -0
- package/dist/metrics/collector.d.ts.map +1 -0
- package/dist/metrics/collector.js +103 -0
- package/dist/metrics/collector.js.map +1 -0
- package/dist/polling/updates.controller.d.ts +57 -0
- package/dist/polling/updates.controller.d.ts.map +1 -0
- package/dist/polling/updates.controller.js +70 -0
- package/dist/polling/updates.controller.js.map +1 -0
- package/dist/repository/db-set.d.ts.map +1 -1
- package/dist/repository/db-set.js +12 -8
- package/dist/repository/db-set.js.map +1 -1
- package/dist/repository/ef-core.d.ts.map +1 -1
- package/dist/repository/ef-core.js +6 -6
- package/dist/repository/ef-core.js.map +1 -1
- package/dist/repository/factory.d.ts +1 -1
- package/dist/repository/factory.d.ts.map +1 -1
- package/dist/repository/factory.js.map +1 -1
- package/dist/repository/interfaces.d.ts.map +1 -1
- package/dist/repository/interfaces.js.map +1 -1
- package/dist/repository/queryable.d.ts.map +1 -1
- package/dist/repository/queryable.js.map +1 -1
- package/dist/rpc/adapter.d.ts.map +1 -1
- package/dist/rpc/adapter.js.map +1 -1
- package/dist/rpc/router.d.ts +2 -2
- package/dist/rpc/router.d.ts.map +1 -1
- package/dist/rpc/router.js +1 -1
- package/dist/rpc/router.js.map +1 -1
- package/dist/schema/relationship-types.d.ts +7 -2
- package/dist/schema/relationship-types.js +1 -1
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/services/admin/app-users.service.d.ts +1 -1
- package/dist/services/admin/app-users.service.js +31 -38
- package/dist/services/admin/auth.service.d.ts +1 -1
- package/dist/services/admin/auth.service.js +11 -5
- package/dist/services/admin/backup/BackupOrchestrator.d.ts.map +1 -1
- package/dist/services/admin/backup/BackupOrchestrator.js +4 -7
- package/dist/services/admin/backup/BackupOrchestrator.js.map +1 -1
- package/dist/services/admin/backup/SqliteGenerator.js +8 -8
- package/dist/services/admin/backup/StorageStreamer.d.ts +3 -3
- package/dist/services/admin/backup/StorageStreamer.d.ts.map +1 -1
- package/dist/services/admin/backup/StorageStreamer.js +16 -55
- package/dist/services/admin/backup/StorageStreamer.js.map +1 -1
- package/dist/services/admin/backup/ZipComposer.d.ts +2 -0
- package/dist/services/admin/backup/ZipComposer.d.ts.map +1 -1
- package/dist/services/admin/backup/ZipComposer.js +23 -0
- package/dist/services/admin/backup/ZipComposer.js.map +1 -1
- package/dist/services/admin/backup.service.d.ts.map +1 -1
- package/dist/services/admin/backup.service.js.map +1 -1
- package/dist/services/admin/data.service.d.ts.map +1 -1
- package/dist/services/admin/data.service.js +287 -286
- package/dist/services/admin/data.service.js.map +1 -1
- package/dist/services/admin/tenants.service.d.ts.map +1 -1
- package/dist/services/admin/tenants.service.js.map +1 -1
- package/dist/services/auth.service.d.ts +2 -3
- package/dist/services/auth.service.js +16 -16
- package/dist/services/invite.service.d.ts +1 -1
- package/dist/services/invite.service.js +17 -15
- package/dist/services/storage.service.d.ts.map +1 -1
- package/dist/services/storage.service.js +35 -4
- package/dist/services/storage.service.js.map +1 -1
- package/dist/services/system.service.d.ts.map +1 -1
- package/dist/services/system.service.js +1 -1
- package/dist/services/system.service.js.map +1 -1
- package/dist/services/tenant.service.d.ts +1 -1
- package/dist/services/tenant.service.js +43 -31
- package/dist/sse/subscriptions.controller.d.ts +57 -0
- package/dist/sse/subscriptions.controller.d.ts.map +1 -0
- package/dist/sse/subscriptions.controller.js +127 -0
- package/dist/sse/subscriptions.controller.js.map +1 -0
- package/dist/startup/bootstrap.d.ts +13 -2
- package/dist/startup/bootstrap.d.ts.map +1 -1
- package/dist/startup/bootstrap.js +85 -13
- package/dist/startup/bootstrap.js.map +1 -1
- package/dist/storage/s3-backend.d.ts.map +1 -1
- package/dist/storage/s3-backend.js +3 -3
- package/dist/storage/s3-backend.js.map +1 -1
- package/dist/websocket/server.d.ts.map +1 -1
- package/dist/websocket/server.js +14 -3
- package/dist/websocket/server.js.map +1 -1
- package/docker/README.md +309 -11
- package/package.json +214 -210
- package/templates/.env.example +115 -55
- package/templates/archlast.config.js +51 -37
- package/templates/docker-compose.prod.yml +32 -15
- package/templates/docker-compose.yml +117 -33
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BullMQJobQueue = void 0;
|
|
4
|
+
const bullmq_1 = require("bullmq");
|
|
5
|
+
const cuid2_1 = require("@paralleldrive/cuid2");
|
|
6
|
+
const logger_js_1 = require("../logging/logger.js");
|
|
7
|
+
const collector_js_1 = require("../metrics/collector.js");
|
|
8
|
+
const redis_connection_js_1 = require("./redis-connection.js");
|
|
9
|
+
const worker_js_1 = require("./worker.js");
|
|
10
|
+
/**
|
|
11
|
+
* Converts priority from system format (1-20, lower=higher priority)
|
|
12
|
+
* to BullMQ format (1-2097152, lower=higher priority)
|
|
13
|
+
*/
|
|
14
|
+
function toBullMQPriority(priority) {
|
|
15
|
+
// Map 1-20 to 2097152-1 (higher priority = lower number in BullMQ)
|
|
16
|
+
// 1 -> 2097152 (highest priority in BullMQ)
|
|
17
|
+
// 20 -> 104857 (lowest priority in BullMQ)
|
|
18
|
+
const normalizedPriority = Math.max(1, Math.min(20, priority));
|
|
19
|
+
return Math.floor((21 - normalizedPriority) * 104857.6);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Converts priority from BullMQ format back to system format
|
|
23
|
+
*/
|
|
24
|
+
function fromBullMQPriority(bullmqPriority) {
|
|
25
|
+
// Reverse the mapping
|
|
26
|
+
const systemPriority = 21 - Math.ceil(bullmqPriority / 104857.6);
|
|
27
|
+
return Math.max(1, Math.min(20, systemPriority));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* BullMQ-based implementation of the JobQueue interface.
|
|
31
|
+
* Uses BullMQ Queue for job management and a separate DLQ queue for failed jobs.
|
|
32
|
+
*/
|
|
33
|
+
class BullMQJobQueue {
|
|
34
|
+
queue;
|
|
35
|
+
dlqQueue;
|
|
36
|
+
defaultMaxAttempts;
|
|
37
|
+
backoffBaseMs;
|
|
38
|
+
queueName;
|
|
39
|
+
disposed = false;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.queueName = options.queueName ?? "jobs";
|
|
42
|
+
this.defaultMaxAttempts = options.defaultMaxAttempts ?? 5;
|
|
43
|
+
this.backoffBaseMs = options.backoffBaseMs ?? 30_000;
|
|
44
|
+
const connection = (0, redis_connection_js_1.createBullMQProducerConnection)();
|
|
45
|
+
this.queue = new bullmq_1.Queue(this.queueName, {
|
|
46
|
+
connection,
|
|
47
|
+
defaultJobOptions: {
|
|
48
|
+
removeOnComplete: false,
|
|
49
|
+
removeOnFail: false,
|
|
50
|
+
attempts: this.defaultMaxAttempts,
|
|
51
|
+
backoff: {
|
|
52
|
+
type: "fixed",
|
|
53
|
+
delay: this.backoffBaseMs,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
this.dlqQueue = new bullmq_1.Queue(`${this.queueName}-dlq`, {
|
|
58
|
+
connection,
|
|
59
|
+
defaultJobOptions: {
|
|
60
|
+
removeOnComplete: false,
|
|
61
|
+
removeOnFail: false,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Enqueue a new job
|
|
67
|
+
*/
|
|
68
|
+
async enqueue(name, payload, options) {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const runAfterMs = options?.runAfterMs ?? 0;
|
|
71
|
+
const delay = runAfterMs > 0 ? runAfterMs : undefined;
|
|
72
|
+
const maxAttempts = options?.maxAttempts ?? this.defaultMaxAttempts;
|
|
73
|
+
const priority = options?.priority ?? 5;
|
|
74
|
+
const timeoutMs = options?.timeoutMs ?? 300000;
|
|
75
|
+
const jobData = {
|
|
76
|
+
name,
|
|
77
|
+
payload,
|
|
78
|
+
originalPriority: priority,
|
|
79
|
+
attempts: 0,
|
|
80
|
+
maxAttempts,
|
|
81
|
+
createdAt: now,
|
|
82
|
+
dependsOn: options?.dependsOn,
|
|
83
|
+
timeoutMs,
|
|
84
|
+
};
|
|
85
|
+
const bullmqJob = await this.queue.add(name, jobData, {
|
|
86
|
+
delay,
|
|
87
|
+
priority: toBullMQPriority(priority),
|
|
88
|
+
attempts: maxAttempts,
|
|
89
|
+
backoff: {
|
|
90
|
+
type: "fixed",
|
|
91
|
+
delay: this.backoffBaseMs,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
const id = bullmqJob.id ?? (0, cuid2_1.createId)();
|
|
95
|
+
// Update metrics
|
|
96
|
+
collector_js_1.jobMetrics.jobsPending.inc({ priority_level: String(priority) });
|
|
97
|
+
logger_js_1.logger.log({
|
|
98
|
+
timestamp: Date.now(),
|
|
99
|
+
level: "info",
|
|
100
|
+
kind: "system",
|
|
101
|
+
message: `[BullMQJobQueue] Job enqueued: ${id} (${name})`,
|
|
102
|
+
});
|
|
103
|
+
return id;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Claim the next available job for execution.
|
|
107
|
+
* Note: In BullMQ, workers handle job claiming automatically.
|
|
108
|
+
* This method is kept for interface compatibility but returns null
|
|
109
|
+
* as BullMQ workers handle the actual claiming.
|
|
110
|
+
*/
|
|
111
|
+
async claimNext() {
|
|
112
|
+
// BullMQ workers handle job claiming automatically
|
|
113
|
+
// This method exists for interface compatibility
|
|
114
|
+
// To get a job manually, we'd need to use queue.getNextJob() which requires
|
|
115
|
+
// a worker token - this is handled by the Worker class
|
|
116
|
+
logger_js_1.logger.log({
|
|
117
|
+
timestamp: Date.now(),
|
|
118
|
+
level: "info",
|
|
119
|
+
kind: "system",
|
|
120
|
+
message: `[BullMQJobQueue] claimNext() called - BullMQ workers handle this automatically`,
|
|
121
|
+
});
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Mark a job as completed
|
|
126
|
+
*/
|
|
127
|
+
async complete(id, jobName) {
|
|
128
|
+
// In BullMQ, jobs are marked complete by the worker
|
|
129
|
+
// This method updates metrics and logging for compatibility
|
|
130
|
+
if (jobName) {
|
|
131
|
+
collector_js_1.jobMetrics.jobsCompleted.inc({ job_name: jobName, status: "completed" });
|
|
132
|
+
}
|
|
133
|
+
collector_js_1.jobMetrics.jobsActive.dec();
|
|
134
|
+
logger_js_1.logger.log({
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
level: "info",
|
|
137
|
+
kind: "system",
|
|
138
|
+
message: `[BullMQJobQueue] Job completed: ${id}`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Handle job failure - either retry or move to DLQ
|
|
143
|
+
*/
|
|
144
|
+
async fail(job, error, backoffMs) {
|
|
145
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
146
|
+
const attempts = job.attempts;
|
|
147
|
+
const maxAttempts = job.maxAttempts;
|
|
148
|
+
const errorCategory = this.categorizeError(error);
|
|
149
|
+
if (attempts >= maxAttempts) {
|
|
150
|
+
// Move to DLQ
|
|
151
|
+
await this.moveToDLQ(job, error, errorCategory);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Schedule retry - BullMQ handles this automatically
|
|
155
|
+
// We just log and update metrics
|
|
156
|
+
const delay = backoffMs ?? this.backoffBaseMs * attempts;
|
|
157
|
+
collector_js_1.jobMetrics.jobsRetried.inc({ job_name: job.name });
|
|
158
|
+
collector_js_1.jobMetrics.jobsActive.dec();
|
|
159
|
+
logger_js_1.logger.log({
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
level: "warn",
|
|
162
|
+
kind: "system",
|
|
163
|
+
message: `[BullMQJobQueue] Job rescheduled: ${job.id} (attempt ${attempts}/${maxAttempts}, next run in ${delay}ms)`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Move a job to the Dead Letter Queue
|
|
169
|
+
*/
|
|
170
|
+
async moveToDLQ(job, error, errorCategory) {
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
173
|
+
const dlqData = {
|
|
174
|
+
name: job.name,
|
|
175
|
+
payload: job.payload,
|
|
176
|
+
originalPriority: job.priority ?? 5,
|
|
177
|
+
attempts: job.attempts,
|
|
178
|
+
maxAttempts: job.maxAttempts,
|
|
179
|
+
createdAt: job.createdAt,
|
|
180
|
+
dependsOn: job.dependsOn,
|
|
181
|
+
timeoutMs: job.timeoutMs ?? 300000,
|
|
182
|
+
jobId: job.id,
|
|
183
|
+
originalJobId: job.id,
|
|
184
|
+
deadLetteredAt: now,
|
|
185
|
+
errorCategory,
|
|
186
|
+
diagnostics: this.extractDiagnostics(error),
|
|
187
|
+
reprocessCount: 0,
|
|
188
|
+
lastError: errorMsg,
|
|
189
|
+
};
|
|
190
|
+
await this.dlqQueue.add(job.name, dlqData);
|
|
191
|
+
// Update metrics
|
|
192
|
+
collector_js_1.jobMetrics.jobsFailed.inc({
|
|
193
|
+
job_name: job.name,
|
|
194
|
+
error_category: errorCategory,
|
|
195
|
+
});
|
|
196
|
+
collector_js_1.jobMetrics.jobsInDLQ.inc({ error_category: errorCategory });
|
|
197
|
+
collector_js_1.jobMetrics.jobsActive.dec();
|
|
198
|
+
logger_js_1.logger.log({
|
|
199
|
+
timestamp: Date.now(),
|
|
200
|
+
level: "error",
|
|
201
|
+
kind: "system",
|
|
202
|
+
message: `[BullMQJobQueue] Job moved to DLQ: ${job.id} (${job.name}) - ${errorCategory}: ${errorMsg}`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Categorize error for DLQ classification
|
|
207
|
+
*/
|
|
208
|
+
categorizeError(error) {
|
|
209
|
+
if (error instanceof worker_js_1.JobTimeoutError) {
|
|
210
|
+
return "timeout";
|
|
211
|
+
}
|
|
212
|
+
if (error instanceof Error) {
|
|
213
|
+
const msg = error.message.toLowerCase();
|
|
214
|
+
if (msg.includes("timeout") || msg.includes("timed out")) {
|
|
215
|
+
return "timeout";
|
|
216
|
+
}
|
|
217
|
+
if (msg.includes("validation") || msg.includes("invalid")) {
|
|
218
|
+
return "validation";
|
|
219
|
+
}
|
|
220
|
+
if (msg.includes("dependency") || msg.includes("depends on")) {
|
|
221
|
+
return "dependency";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return "system";
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Extract diagnostic information from error
|
|
228
|
+
*/
|
|
229
|
+
extractDiagnostics(error) {
|
|
230
|
+
const diagnostics = {
|
|
231
|
+
timestamp: Date.now(),
|
|
232
|
+
};
|
|
233
|
+
if (error instanceof Error) {
|
|
234
|
+
diagnostics.name = error.name;
|
|
235
|
+
diagnostics.message = error.message;
|
|
236
|
+
diagnostics.stack = error.stack;
|
|
237
|
+
}
|
|
238
|
+
return diagnostics;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get a job by ID
|
|
242
|
+
*/
|
|
243
|
+
async getJob(id) {
|
|
244
|
+
// BullMQ uses job IDs as numbers internally
|
|
245
|
+
const job = await this.queue.getJob(id);
|
|
246
|
+
if (!job)
|
|
247
|
+
return null;
|
|
248
|
+
return this.mapBullMQJobToJobRecord(job);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* List jobs by status
|
|
252
|
+
*/
|
|
253
|
+
async listJobs(status) {
|
|
254
|
+
let jobs = [];
|
|
255
|
+
switch (status) {
|
|
256
|
+
case "pending":
|
|
257
|
+
jobs = await this.queue.getWaiting();
|
|
258
|
+
break;
|
|
259
|
+
case "active":
|
|
260
|
+
jobs = await this.queue.getActive();
|
|
261
|
+
break;
|
|
262
|
+
case "completed":
|
|
263
|
+
jobs = await this.queue.getCompleted();
|
|
264
|
+
break;
|
|
265
|
+
case "failed":
|
|
266
|
+
jobs = await this.queue.getFailed();
|
|
267
|
+
break;
|
|
268
|
+
default:
|
|
269
|
+
// Get all jobs from all states
|
|
270
|
+
const [waiting, active, completed, failed] = await Promise.all([
|
|
271
|
+
this.queue.getWaiting(),
|
|
272
|
+
this.queue.getActive(),
|
|
273
|
+
this.queue.getCompleted(),
|
|
274
|
+
this.queue.getFailed(),
|
|
275
|
+
]);
|
|
276
|
+
jobs = [...waiting, ...active, ...completed, ...failed];
|
|
277
|
+
}
|
|
278
|
+
return jobs.map((job) => this.mapBullMQJobToJobRecord(job));
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Map BullMQ Job to JobRecord
|
|
282
|
+
*/
|
|
283
|
+
mapBullMQJobToJobRecord(job) {
|
|
284
|
+
const data = job.data;
|
|
285
|
+
const now = Date.now();
|
|
286
|
+
// Determine status based on BullMQ job state
|
|
287
|
+
let status = "pending";
|
|
288
|
+
if (job.failedReason) {
|
|
289
|
+
status = "failed";
|
|
290
|
+
}
|
|
291
|
+
else if (job.returnvalue !== undefined) {
|
|
292
|
+
status = "completed";
|
|
293
|
+
}
|
|
294
|
+
else if (job.processedOn) {
|
|
295
|
+
status = "active";
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
_id: job.id ?? (0, cuid2_1.createId)(),
|
|
299
|
+
id: job.id ?? (0, cuid2_1.createId)(),
|
|
300
|
+
name: data.name,
|
|
301
|
+
payload: data.payload,
|
|
302
|
+
status,
|
|
303
|
+
attempts: job.attemptsMade,
|
|
304
|
+
maxAttempts: data.maxAttempts ?? this.defaultMaxAttempts,
|
|
305
|
+
runAfter: job.delay ? data.createdAt + job.delay : data.createdAt,
|
|
306
|
+
createdAt: data.createdAt,
|
|
307
|
+
updatedAt: job.processedOn ?? now,
|
|
308
|
+
lastError: job.failedReason,
|
|
309
|
+
priority: data.originalPriority,
|
|
310
|
+
timeoutMs: data.timeoutMs,
|
|
311
|
+
dependsOn: data.dependsOn,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
// ========== DLQ Management Methods ==========
|
|
315
|
+
/**
|
|
316
|
+
* List DLQ jobs with optional filtering and pagination
|
|
317
|
+
*/
|
|
318
|
+
async getDLQJobs(options) {
|
|
319
|
+
const { limit = 100, offset = 0, errorCategory, jobName } = options ?? {};
|
|
320
|
+
// Get all DLQ jobs (BullMQ doesn't support offset/limit natively for getJobs)
|
|
321
|
+
const jobs = await this.dlqQueue.getJobs(["waiting", "active", "completed", "failed"]);
|
|
322
|
+
// Map and filter
|
|
323
|
+
let dlqJobs = jobs.map((job) => this.mapBullMQJobToDLQRecord(job));
|
|
324
|
+
if (errorCategory) {
|
|
325
|
+
dlqJobs = dlqJobs.filter((j) => j.errorCategory === errorCategory);
|
|
326
|
+
}
|
|
327
|
+
if (jobName) {
|
|
328
|
+
dlqJobs = dlqJobs.filter((j) => j.name === jobName);
|
|
329
|
+
}
|
|
330
|
+
// Sort by deadLetteredAt descending and apply pagination
|
|
331
|
+
dlqJobs.sort((a, b) => b.deadLetteredAt - a.deadLetteredAt);
|
|
332
|
+
logger_js_1.logger.log({
|
|
333
|
+
timestamp: Date.now(),
|
|
334
|
+
level: "info",
|
|
335
|
+
kind: "system",
|
|
336
|
+
message: `[BullMQJobQueue] Listed ${dlqJobs.length} DLQ jobs`,
|
|
337
|
+
});
|
|
338
|
+
return dlqJobs.slice(offset, offset + limit);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Map BullMQ Job to JobDLQRecord
|
|
342
|
+
*/
|
|
343
|
+
mapBullMQJobToDLQRecord(job) {
|
|
344
|
+
const data = job.data;
|
|
345
|
+
const now = Date.now();
|
|
346
|
+
return {
|
|
347
|
+
_id: job.id ?? (0, cuid2_1.createId)(),
|
|
348
|
+
id: job.id ?? (0, cuid2_1.createId)(),
|
|
349
|
+
name: data.name,
|
|
350
|
+
payload: data.payload,
|
|
351
|
+
status: "failed",
|
|
352
|
+
attempts: job.attemptsMade,
|
|
353
|
+
maxAttempts: data.maxAttempts ?? this.defaultMaxAttempts,
|
|
354
|
+
runAfter: data.createdAt,
|
|
355
|
+
createdAt: data.createdAt,
|
|
356
|
+
updatedAt: job.processedOn ?? now,
|
|
357
|
+
lastError: data.lastError ?? job.failedReason,
|
|
358
|
+
priority: data.originalPriority,
|
|
359
|
+
timeoutMs: data.timeoutMs,
|
|
360
|
+
dependsOn: data.dependsOn,
|
|
361
|
+
originalJobId: data.originalJobId ?? job.id ?? (0, cuid2_1.createId)(),
|
|
362
|
+
deadLetteredAt: data.deadLetteredAt ?? now,
|
|
363
|
+
errorCategory: data.errorCategory ?? "system",
|
|
364
|
+
diagnostics: data.diagnostics ?? {},
|
|
365
|
+
reprocessCount: data.reprocessCount ?? 0,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Get specific DLQ job by ID
|
|
370
|
+
*/
|
|
371
|
+
async getDLQJob(id) {
|
|
372
|
+
const job = await this.dlqQueue.getJob(id);
|
|
373
|
+
if (!job)
|
|
374
|
+
return null;
|
|
375
|
+
return this.mapBullMQJobToDLQRecord(job);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Get DLQ statistics
|
|
379
|
+
*/
|
|
380
|
+
async getDLQStats() {
|
|
381
|
+
const jobs = await this.dlqQueue.getJobs(["waiting", "active", "completed", "failed"]);
|
|
382
|
+
// Map server error categories to client-expected categories
|
|
383
|
+
const categoryMapping = {
|
|
384
|
+
timeout: "timeout",
|
|
385
|
+
validation: "validation",
|
|
386
|
+
dependency: "unknown",
|
|
387
|
+
system: "execution",
|
|
388
|
+
};
|
|
389
|
+
const byErrorCategory = {};
|
|
390
|
+
const byStatus = {};
|
|
391
|
+
const byJobName = {};
|
|
392
|
+
const now = Date.now();
|
|
393
|
+
let oldestFailure = null;
|
|
394
|
+
let recentFailures24h = 0;
|
|
395
|
+
const twentyFourHoursAgo = now - 24 * 60 * 60 * 1000;
|
|
396
|
+
for (const job of jobs) {
|
|
397
|
+
const data = job.data;
|
|
398
|
+
// Map and count by error category
|
|
399
|
+
const rawCategory = data.errorCategory ?? "system";
|
|
400
|
+
const mappedCategory = categoryMapping[rawCategory] ?? "unknown";
|
|
401
|
+
byErrorCategory[mappedCategory] = (byErrorCategory[mappedCategory] ?? 0) + 1;
|
|
402
|
+
// Count by status (based on reprocessCount)
|
|
403
|
+
const status = (data.reprocessCount ?? 0) > 0 ? "retried" : "pending";
|
|
404
|
+
byStatus[status] = (byStatus[status] ?? 0) + 1;
|
|
405
|
+
// Count by job name
|
|
406
|
+
const name = data.name ?? "unnamed";
|
|
407
|
+
byJobName[name] = (byJobName[name] ?? 0) + 1;
|
|
408
|
+
// Track oldest failure timestamp
|
|
409
|
+
const deadLetteredAt = data.deadLetteredAt ?? now;
|
|
410
|
+
if (oldestFailure === null || deadLetteredAt < oldestFailure) {
|
|
411
|
+
oldestFailure = deadLetteredAt;
|
|
412
|
+
}
|
|
413
|
+
// Count failures in last 24 hours
|
|
414
|
+
if (deadLetteredAt >= twentyFourHoursAgo) {
|
|
415
|
+
recentFailures24h++;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
totalDLQJobs: jobs.length,
|
|
420
|
+
byStatus,
|
|
421
|
+
byErrorCategory,
|
|
422
|
+
byJobName,
|
|
423
|
+
oldestFailure,
|
|
424
|
+
recentFailures24h,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Reprocess a job from DLQ back to main queue
|
|
429
|
+
*/
|
|
430
|
+
async reprocessFromDLQ(dlqJobId, options) {
|
|
431
|
+
// Retrieve DLQ job
|
|
432
|
+
const dlqJob = await this.getDLQJob(dlqJobId);
|
|
433
|
+
if (!dlqJob) {
|
|
434
|
+
throw new Error(`DLQ job not found: ${dlqJobId}`);
|
|
435
|
+
}
|
|
436
|
+
// Create new job in main queue with same payload
|
|
437
|
+
const newJobId = await this.enqueue(dlqJob.name, dlqJob.payload, {
|
|
438
|
+
maxAttempts: options?.newMaxAttempts ?? dlqJob.maxAttempts,
|
|
439
|
+
priority: options?.priority ?? dlqJob.priority,
|
|
440
|
+
});
|
|
441
|
+
// Update reprocess count in DLQ record
|
|
442
|
+
const existingJob = await this.dlqQueue.getJob(dlqJobId);
|
|
443
|
+
if (existingJob) {
|
|
444
|
+
const data = existingJob.data;
|
|
445
|
+
data.reprocessCount = (data.reprocessCount ?? 0) + 1;
|
|
446
|
+
// Note: BullMQ doesn't support updating job data directly,
|
|
447
|
+
// so we rely on the in-memory modification for subsequent reads
|
|
448
|
+
}
|
|
449
|
+
logger_js_1.logger.log({
|
|
450
|
+
timestamp: Date.now(),
|
|
451
|
+
level: "info",
|
|
452
|
+
kind: "system",
|
|
453
|
+
message: `[BullMQJobQueue] Reprocessed DLQ job ${dlqJobId} -> new job ${newJobId}`,
|
|
454
|
+
});
|
|
455
|
+
return newJobId;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Delete specific DLQ job
|
|
459
|
+
*/
|
|
460
|
+
async deleteFromDLQ(id) {
|
|
461
|
+
const job = await this.dlqQueue.getJob(id);
|
|
462
|
+
if (!job) {
|
|
463
|
+
throw new Error(`DLQ job not found: ${id}`);
|
|
464
|
+
}
|
|
465
|
+
await job.remove();
|
|
466
|
+
logger_js_1.logger.log({
|
|
467
|
+
timestamp: Date.now(),
|
|
468
|
+
level: "info",
|
|
469
|
+
kind: "system",
|
|
470
|
+
message: `[BullMQJobQueue] Deleted DLQ job: ${id}`,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Purge DLQ jobs older than specified time
|
|
475
|
+
*/
|
|
476
|
+
async purgeDLQ(olderThanMs = 30 * 24 * 60 * 60 * 1000) {
|
|
477
|
+
const now = Date.now();
|
|
478
|
+
const cutoffTime = now - olderThanMs;
|
|
479
|
+
// Get all DLQ jobs
|
|
480
|
+
const jobs = await this.dlqQueue.getJobs(["waiting", "active", "completed", "failed"]);
|
|
481
|
+
// Filter jobs older than cutoff
|
|
482
|
+
const toDelete = jobs.filter((job) => {
|
|
483
|
+
const data = job.data;
|
|
484
|
+
const deadLetteredAt = data.deadLetteredAt ?? now;
|
|
485
|
+
return deadLetteredAt < cutoffTime;
|
|
486
|
+
});
|
|
487
|
+
// Delete each job
|
|
488
|
+
for (const job of toDelete) {
|
|
489
|
+
await job.remove();
|
|
490
|
+
}
|
|
491
|
+
logger_js_1.logger.log({
|
|
492
|
+
timestamp: Date.now(),
|
|
493
|
+
level: "info",
|
|
494
|
+
kind: "system",
|
|
495
|
+
message: `[BullMQJobQueue] Purged ${toDelete.length} DLQ jobs older than ${olderThanMs}ms`,
|
|
496
|
+
});
|
|
497
|
+
return toDelete.length;
|
|
498
|
+
}
|
|
499
|
+
// ========== Statistics and Priority Methods ==========
|
|
500
|
+
/**
|
|
501
|
+
* Get queue statistics
|
|
502
|
+
*/
|
|
503
|
+
async getQueueStats() {
|
|
504
|
+
const [waiting, active, completed, failed, dlqJobs] = await Promise.all([
|
|
505
|
+
this.queue.getWaiting(),
|
|
506
|
+
this.queue.getActive(),
|
|
507
|
+
this.queue.getCompleted(),
|
|
508
|
+
this.queue.getFailed(),
|
|
509
|
+
this.dlqQueue.getJobs(["waiting", "active", "completed", "failed"]),
|
|
510
|
+
]);
|
|
511
|
+
const priorityDistribution = {};
|
|
512
|
+
let totalProcessingTime = 0;
|
|
513
|
+
let completedCount = 0;
|
|
514
|
+
// Count all jobs by priority
|
|
515
|
+
for (const job of [...waiting, ...active, ...completed, ...failed]) {
|
|
516
|
+
const priority = String(job.data.originalPriority ?? 5);
|
|
517
|
+
priorityDistribution[priority] = (priorityDistribution[priority] || 0) + 1;
|
|
518
|
+
// Calculate processing time for completed jobs
|
|
519
|
+
if (job.returnvalue !== undefined && job.processedOn && job.timestamp) {
|
|
520
|
+
const processingTime = job.processedOn - job.timestamp;
|
|
521
|
+
totalProcessingTime += processingTime;
|
|
522
|
+
completedCount++;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const avgProcessingTime = completedCount > 0 ? totalProcessingTime / completedCount : 0;
|
|
526
|
+
return {
|
|
527
|
+
totalJobs: waiting.length + active.length + completed.length + failed.length,
|
|
528
|
+
pendingJobs: waiting.length,
|
|
529
|
+
activeJobs: active.length,
|
|
530
|
+
completedJobs: completed.length,
|
|
531
|
+
failedJobs: failed.length,
|
|
532
|
+
dlqJobs: dlqJobs.length,
|
|
533
|
+
avgProcessingTime,
|
|
534
|
+
priorityDistribution,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Get queue depth by priority level
|
|
539
|
+
*/
|
|
540
|
+
async getQueueDepthByPriority() {
|
|
541
|
+
const waiting = await this.queue.getWaiting();
|
|
542
|
+
const depthByPriority = {};
|
|
543
|
+
for (const job of waiting) {
|
|
544
|
+
const priority = job.data.originalPriority ?? 5;
|
|
545
|
+
depthByPriority[priority] = (depthByPriority[priority] || 0) + 1;
|
|
546
|
+
}
|
|
547
|
+
// Convert to array and sort by priority (descending - higher priority first)
|
|
548
|
+
return Object.entries(depthByPriority)
|
|
549
|
+
.map(([priority, count]) => ({
|
|
550
|
+
priority: parseInt(priority),
|
|
551
|
+
count,
|
|
552
|
+
label: this.getPriorityLabel(parseInt(priority)),
|
|
553
|
+
}))
|
|
554
|
+
.sort((a, b) => b.priority - a.priority);
|
|
555
|
+
}
|
|
556
|
+
getPriorityLabel(priority) {
|
|
557
|
+
if (priority >= 20)
|
|
558
|
+
return "urgent";
|
|
559
|
+
if (priority >= 10)
|
|
560
|
+
return "high";
|
|
561
|
+
if (priority >= 5)
|
|
562
|
+
return "normal";
|
|
563
|
+
return "low";
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Update job priority
|
|
567
|
+
* Note: BullMQ doesn't support changing priority after job creation,
|
|
568
|
+
* so we remove and re-add the job with the new priority.
|
|
569
|
+
*/
|
|
570
|
+
async updateJobPriority(jobId, priority) {
|
|
571
|
+
// Validate priority range (1-20)
|
|
572
|
+
if (priority < 1 || priority > 20) {
|
|
573
|
+
throw new Error("Priority must be between 1 and 20");
|
|
574
|
+
}
|
|
575
|
+
const job = await this.queue.getJob(jobId);
|
|
576
|
+
if (!job) {
|
|
577
|
+
throw new Error(`Job not found: ${jobId}`);
|
|
578
|
+
}
|
|
579
|
+
// BullMQ doesn't support updating job priority directly
|
|
580
|
+
// We need to remove and re-add the job
|
|
581
|
+
const data = job.data;
|
|
582
|
+
const delay = job.delay > 0 ? job.delay - (Date.now() - job.timestamp) : 0;
|
|
583
|
+
// Remove old job
|
|
584
|
+
await job.remove();
|
|
585
|
+
// Add new job with updated priority
|
|
586
|
+
await this.queue.add(data.name, { ...data, originalPriority: priority }, {
|
|
587
|
+
delay: delay > 0 ? delay : undefined,
|
|
588
|
+
priority: toBullMQPriority(priority),
|
|
589
|
+
attempts: data.maxAttempts - job.attemptsMade,
|
|
590
|
+
backoff: {
|
|
591
|
+
type: "fixed",
|
|
592
|
+
delay: this.backoffBaseMs,
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
logger_js_1.logger.log({
|
|
596
|
+
timestamp: Date.now(),
|
|
597
|
+
level: "info",
|
|
598
|
+
kind: "system",
|
|
599
|
+
message: `[BullMQJobQueue] Job priority updated: ${jobId} -> ${priority}`,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
// ========== Stuck Job Management ==========
|
|
603
|
+
/**
|
|
604
|
+
* Find jobs that are stuck in "active" status (worker terminated)
|
|
605
|
+
*/
|
|
606
|
+
async findStuckJobs(timeoutMs = 300000) {
|
|
607
|
+
const now = Date.now();
|
|
608
|
+
const staleThreshold = now - timeoutMs;
|
|
609
|
+
const active = await this.queue.getActive();
|
|
610
|
+
const stuckJobs = active
|
|
611
|
+
.filter((job) => {
|
|
612
|
+
// Job is stuck if processedOn is older than threshold
|
|
613
|
+
return job.processedOn && job.processedOn < staleThreshold;
|
|
614
|
+
})
|
|
615
|
+
.map((job) => this.mapBullMQJobToJobRecord(job));
|
|
616
|
+
return stuckJobs;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Reset a stuck job from "active" to "pending" status
|
|
620
|
+
*/
|
|
621
|
+
async resetStuckJob(jobId) {
|
|
622
|
+
const job = await this.queue.getJob(jobId);
|
|
623
|
+
if (!job) {
|
|
624
|
+
throw new Error(`Job not found: ${jobId}`);
|
|
625
|
+
}
|
|
626
|
+
// In BullMQ, we move the job back to waiting state
|
|
627
|
+
// Note: This is a workaround since BullMQ doesn't have a direct "reset" method
|
|
628
|
+
const data = job.data;
|
|
629
|
+
// Remove the stuck job and re-add it
|
|
630
|
+
await job.remove();
|
|
631
|
+
await this.queue.add(data.name, data, {
|
|
632
|
+
priority: toBullMQPriority(data.originalPriority),
|
|
633
|
+
attempts: data.maxAttempts - job.attemptsMade,
|
|
634
|
+
backoff: {
|
|
635
|
+
type: "fixed",
|
|
636
|
+
delay: this.backoffBaseMs,
|
|
637
|
+
},
|
|
638
|
+
});
|
|
639
|
+
logger_js_1.logger.log({
|
|
640
|
+
timestamp: Date.now(),
|
|
641
|
+
level: "info",
|
|
642
|
+
kind: "system",
|
|
643
|
+
message: `[BullMQJobQueue] Reset stuck job: ${jobId} from active to pending`,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Bulk reset multiple stuck jobs
|
|
648
|
+
*/
|
|
649
|
+
async resetStuckJobs(jobIds) {
|
|
650
|
+
let reset = 0;
|
|
651
|
+
let failed = 0;
|
|
652
|
+
for (const jobId of jobIds) {
|
|
653
|
+
try {
|
|
654
|
+
await this.resetStuckJob(jobId);
|
|
655
|
+
reset++;
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
logger_js_1.logger.log({
|
|
659
|
+
timestamp: Date.now(),
|
|
660
|
+
level: "error",
|
|
661
|
+
kind: "system",
|
|
662
|
+
message: `[BullMQJobQueue] Failed to reset stuck job ${jobId}: ${error}`,
|
|
663
|
+
});
|
|
664
|
+
failed++;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return { reset, failed };
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Dispose of the queue connections
|
|
671
|
+
*/
|
|
672
|
+
dispose() {
|
|
673
|
+
if (this.disposed)
|
|
674
|
+
return;
|
|
675
|
+
this.queue.close();
|
|
676
|
+
this.dlqQueue.close();
|
|
677
|
+
this.disposed = true;
|
|
678
|
+
logger_js_1.logger.log({
|
|
679
|
+
timestamp: Date.now(),
|
|
680
|
+
level: "info",
|
|
681
|
+
kind: "system",
|
|
682
|
+
message: `[BullMQJobQueue] Disposed queues`,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
exports.BullMQJobQueue = BullMQJobQueue;
|
|
687
|
+
exports.default = BullMQJobQueue;
|
|
688
|
+
//# sourceMappingURL=bullmq-adapter.js.map
|