@yrpri/api 9.0.193 → 9.0.194

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/models/audio.cjs CHANGED
@@ -5,9 +5,24 @@ const _ = require('lodash');
5
5
  const queue = require('../services/workers/queue.cjs');
6
6
  let bullAudioQueue;
7
7
  if (process.env.USE_YOUR_PRIORITIES_ENCODER) {
8
- const Queue = require('bull');
9
- const redisUrl = process.env.REDIS_URL ? process.env.REDIS_URL : undefined;
10
- bullAudioQueue = new Queue('AudioEncoding', redisUrl);
8
+ const { Queue } = require('bullmq');
9
+ let redisUrl = process.env.REDIS_URL ? process.env.REDIS_URL : "redis://localhost:6379";
10
+ if (redisUrl.startsWith("redis://h:")) {
11
+ redisUrl = redisUrl.replace("redis://h:", "redis://:");
12
+ }
13
+ const parsedRedisUrl = new URL(redisUrl);
14
+ const redisConnection = {
15
+ host: parsedRedisUrl.hostname,
16
+ port: parseInt(parsedRedisUrl.port) || 6379,
17
+ password: parsedRedisUrl.password || undefined,
18
+ username: parsedRedisUrl.username || undefined,
19
+ };
20
+ if (redisUrl.startsWith("rediss://")) {
21
+ redisConnection.tls = {
22
+ rejectUnauthorized: false,
23
+ };
24
+ }
25
+ bullAudioQueue = new Queue('AudioEncoding', { connection: redisConnection });
11
26
  }
12
27
  module.exports = (sequelize, DataTypes) => {
13
28
  const Audio = sequelize.define("Audio", {
@@ -393,7 +408,7 @@ module.exports = (sequelize, DataTypes) => {
393
408
  jobPackage = _.merge(jobPackage, {
394
409
  acBackgroundJobId: jobId,
395
410
  });
396
- await bullAudioQueue.add(jobPackage);
411
+ await bullAudioQueue.add('audio-encoding', jobPackage);
397
412
  callback(null, { Job: { Id: jobId } });
398
413
  }
399
414
  });
package/models/video.cjs CHANGED
@@ -6,9 +6,24 @@ const _ = require("lodash");
6
6
  const queue = require("../services/workers/queue.cjs");
7
7
  let bullVideoQueue;
8
8
  if (process.env.USE_YOUR_PRIORITIES_ENCODER) {
9
- const Queue = require("bull");
10
- const redisUrl = process.env.REDIS_URL ? process.env.REDIS_URL : undefined;
11
- bullVideoQueue = new Queue("VideoEncoding", redisUrl);
9
+ const { Queue } = require("bullmq");
10
+ let redisUrl = process.env.REDIS_URL ? process.env.REDIS_URL : "redis://localhost:6379";
11
+ if (redisUrl.startsWith("redis://h:")) {
12
+ redisUrl = redisUrl.replace("redis://h:", "redis://:");
13
+ }
14
+ const parsedRedisUrl = new URL(redisUrl);
15
+ const redisConnection = {
16
+ host: parsedRedisUrl.hostname,
17
+ port: parseInt(parsedRedisUrl.port) || 6379,
18
+ password: parsedRedisUrl.password || undefined,
19
+ username: parsedRedisUrl.username || undefined,
20
+ };
21
+ if (redisUrl.startsWith("rediss://")) {
22
+ redisConnection.tls = {
23
+ rejectUnauthorized: false,
24
+ };
25
+ }
26
+ bullVideoQueue = new Queue("VideoEncoding", { connection: redisConnection });
12
27
  }
13
28
  module.exports = (sequelize, DataTypes) => {
14
29
  const Video = sequelize.define("Video", {
@@ -1077,7 +1092,7 @@ module.exports = (sequelize, DataTypes) => {
1077
1092
  jobPackage = _.merge(jobPackage, {
1078
1093
  acBackgroundJobId: jobId,
1079
1094
  });
1080
- await bullVideoQueue.add(jobPackage);
1095
+ await bullVideoQueue.add('video-encoding', jobPackage);
1081
1096
  callback(null, { Job: { Id: jobId } });
1082
1097
  }
1083
1098
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yrpri/api",
3
- "version": "9.0.193",
3
+ "version": "9.0.194",
4
4
  "license": "MIT",
5
5
  "author": "Robert Bjarnason & Citizens Foundation",
6
6
  "repository": {
@@ -32,7 +32,7 @@
32
32
  "axios": "^1.9.0",
33
33
  "bcrypt": "^5.1.1",
34
34
  "body-parser": "^2.2.0",
35
- "bull": "^4.16.5",
35
+ "bullmq": "^5.31.0",
36
36
  "bunyan": "^1.8.15",
37
37
  "bunyan-prettystream": "git+https://github.com/rbjarnason/node-bunyan-prettystream.git",
38
38
  "cheerio": "^1.0.0",
@@ -5,39 +5,79 @@ var airbrake = null;
5
5
  if (process.env.AIRBRAKE_PROJECT_ID) {
6
6
  airbrake = require('../utils/airbrake.cjs');
7
7
  }
8
- const BullQueue = require('bull');
8
+ const { Queue, Worker } = require('bullmq');
9
9
  let redisUrl = process.env.REDIS_URL ? process.env.REDIS_URL : "redis://localhost:6379";
10
10
  if (redisUrl.startsWith("redis://h:")) {
11
11
  redisUrl = redisUrl.replace("redis://h:", "redis://:");
12
12
  }
13
13
  const EventEmitter = require('events');
14
14
  EventEmitter.defaultMaxListeners = 100;
15
- const redissOptions = {
16
- tls: {
17
- rejectUnauthorized: false,
18
- },
15
+ // Parse redis URL to get connection options
16
+ const parsedRedisUrl = new URL(redisUrl);
17
+ const redisConnection = {
18
+ host: parsedRedisUrl.hostname,
19
+ port: parseInt(parsedRedisUrl.port) || 6379,
20
+ password: parsedRedisUrl.password || undefined,
21
+ username: parsedRedisUrl.username || undefined,
19
22
  };
20
- log.info("Starting app access to Bull Queue", { redis_url: redisUrl });
23
+ // Add TLS options for rediss:// URLs
24
+ if (redisUrl.startsWith("rediss://")) {
25
+ redisConnection.tls = {
26
+ rejectUnauthorized: false,
27
+ };
28
+ }
29
+ log.info("Starting app access to BullMQ Queue", { redis_url: redisUrl });
21
30
  class YpQueue {
22
31
  constructor() {
23
32
  log.info("Create YpQueue");
24
- this.createQueues();
33
+ this.processors = new Map(); // Store processors per job name
34
+ this.workers = new Map(); // Store workers per job name
35
+ this.queues = new Map(); // Store queues per job name
36
+ this.queuePrefix = 'mainYpQueue';
25
37
  }
26
- get defaultQueueOptions() {
38
+ get defaultJobOptions() {
27
39
  return {
28
- redis: redisUrl.includes("rediss://") ? redissOptions : undefined,
29
- defaultJobOptions: {
30
- attempts: 1,
31
- removeOnComplete: true,
32
- removeOnFail: true
33
- }
40
+ attempts: 1,
41
+ removeOnComplete: true,
42
+ removeOnFail: true
43
+ };
44
+ }
45
+ // Wrap callback-style processor to work with BullMQ's promise-based API
46
+ wrapProcessor(processor) {
47
+ return (job) => {
48
+ return new Promise((resolve, reject) => {
49
+ // Call the processor with job and a done callback
50
+ processor(job, (error, result) => {
51
+ if (error) {
52
+ reject(error);
53
+ }
54
+ else {
55
+ resolve(result);
56
+ }
57
+ });
58
+ });
34
59
  };
35
60
  }
36
61
  process(name, concurrency, processor) {
37
- this.mainQueue.process(name, concurrency, processor);
62
+ if (this.workers.has(name)) {
63
+ log.warn('Processor already registered, skipping re-registration', { name });
64
+ return;
65
+ }
66
+ const wrappedProcessor = this.wrapProcessor(processor);
67
+ this.processors.set(name, wrappedProcessor);
68
+ const queue = this.getQueue(name);
69
+ log.info('Registered processor', { name, concurrency, queue: queue.name });
70
+ const worker = new Worker(queue.name, wrappedProcessor, {
71
+ connection: redisConnection,
72
+ concurrency,
73
+ name
74
+ });
75
+ this.workers.set(name, worker);
76
+ this.setupWorkerEvents(worker);
38
77
  }
39
78
  add(name, workPackage, priority, options) {
40
- const jobOptions = options || {};
79
+ const queue = this.getQueue(name);
80
+ const jobOptions = { ...this.defaultJobOptions, ...(options || {}) };
41
81
  let priorityNumber = 1000;
42
82
  switch (priority) {
43
83
  case 'now':
@@ -57,32 +97,51 @@ class YpQueue {
57
97
  break;
58
98
  }
59
99
  jobOptions.priority = priorityNumber;
60
- this.mainQueue.add(name, workPackage, jobOptions);
100
+ queue.add(name, workPackage, jobOptions);
101
+ }
102
+ getQueue(name) {
103
+ if (this.queues.has(name)) {
104
+ return this.queues.get(name);
105
+ }
106
+ const queueName = `${this.queuePrefix}-${name}`;
107
+ const queue = new Queue(queueName, {
108
+ connection: redisConnection,
109
+ defaultJobOptions: this.defaultJobOptions
110
+ });
111
+ queue.on('error', (error) => {
112
+ log.error('Queue Error', { error });
113
+ if (airbrake) {
114
+ if (!(error instanceof Error)) {
115
+ error = new Error(error);
116
+ }
117
+ airbrake.notify(error).then((airbrakeErr) => {
118
+ if (airbrakeErr.error) {
119
+ log.error("AirBrake Error", { context: 'airbrake', err: airbrakeErr.error, errorStatus: 500 });
120
+ }
121
+ });
122
+ }
123
+ });
124
+ this.queues.set(name, queue);
125
+ return queue;
61
126
  }
62
- createQueues() {
63
- this.mainQueue = new BullQueue('mainYpQueue', redisUrl, this.defaultQueueOptions);
64
- this.mainQueue.on('active', function (job) {
127
+ setupWorkerEvents(worker) {
128
+ worker.on('active', (job) => {
65
129
  log.info('JQ', { id: job.id, name: job.name });
66
- }).on('completed', function (job, result) {
130
+ });
131
+ worker.on('completed', (job) => {
67
132
  log.info('JC', { id: job.id, name: job.name });
68
- }).on('failed', function (job, error) {
133
+ });
134
+ worker.on('failed', (job, error) => {
69
135
  log.error('Job Failed', { id: job ? job.id : null, name: job ? job.name : null, data: job ? job.data : null, error });
70
- }).on('resumed', function (job) {
71
- log.info('Job Removed', { id: job.id });
72
- }).on('waiting', function (jobId) {
73
- log.info('Job Waiting', { id: jobId });
74
- }).on('stalled', function (job) {
75
- log.info('Job Stalled', { id: job.id, name: job.name, data: job.data, });
76
- }).on('progress', function (job, process) {
77
- log.info('Job Progress', { id: job.id, process });
78
- }).on('paused', function () {
79
- log.info('Queue Paused');
80
- }).on('cleaned', function (jobs, type) {
81
- log.info('Job Cleaned', { jobs, type });
82
- }).on('drained', function () {
83
- log.info('Queue Drained');
84
- }).on('error', function (error) {
85
- log.error('Job Error', { error });
136
+ });
137
+ worker.on('stalled', (jobId) => {
138
+ log.info('Job Stalled', { id: jobId });
139
+ });
140
+ worker.on('progress', (job, progress) => {
141
+ log.info('Job Progress', { id: job.id, progress });
142
+ });
143
+ worker.on('error', (error) => {
144
+ log.error('Worker Error', { error });
86
145
  if (airbrake) {
87
146
  if (!(error instanceof Error)) {
88
147
  error = new Error(error);
@@ -1,21 +1,18 @@
1
1
  declare const _exports: YpQueue;
2
2
  export = _exports;
3
3
  declare class YpQueue {
4
- get defaultQueueOptions(): {
5
- redis: {
6
- tls: {
7
- rejectUnauthorized: boolean;
8
- };
9
- } | undefined;
10
- defaultJobOptions: {
11
- attempts: number;
12
- removeOnComplete: boolean;
13
- removeOnFail: boolean;
14
- };
4
+ processors: Map<any, any>;
5
+ workers: Map<any, any>;
6
+ queues: Map<any, any>;
7
+ queuePrefix: string;
8
+ get defaultJobOptions(): {
9
+ attempts: number;
10
+ removeOnComplete: boolean;
11
+ removeOnFail: boolean;
15
12
  };
13
+ wrapProcessor(processor: any): (job: any) => Promise<any>;
16
14
  process(name: any, concurrency: any, processor: any): void;
17
15
  add(name: any, workPackage: any, priority: any, options: any): void;
18
- createQueues(): void;
19
- mainQueue: BullQueue.Queue<any> | undefined;
16
+ getQueue(name: any): any;
17
+ setupWorkerEvents(worker: any): void;
20
18
  }
21
- import BullQueue = require("bull");