@yellowpanther/shared 1.2.6 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yellowpanther/shared",
3
- "version": "1.2.6",
3
+ "version": "1.2.9",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -23,6 +23,7 @@
23
23
  "./queue/newsTranslateQueue": "./src/queue/newsTranslateQueue.js",
24
24
  "./queue/newsPublishQueue": "./src/queue/newsPublishQueue.js",
25
25
  "./queue/articlePublishQueue": "./src/queue/articlePublishQueue.js",
26
+ "./queue/eventPublishQueue": "./src/queue/eventPublishQueue.js",
26
27
  "./queue/videoPublishQueue": "./src/queue/videoPublishQueue.js",
27
28
  "./queue/imagePublishQueue": "./src/queue/imagePublishQueue.js",
28
29
  "./queue/imageAlbumPublishQueue": "./src/queue/imageAlbumPublishQueue.js",
package/src/index.js CHANGED
@@ -1,25 +1,33 @@
1
+ const { addEventPublishJob } = require("./queue/eventPublishQueue");
2
+
1
3
  module.exports = {
2
4
  // ✅ Redis
3
- redisClient: require('./redis/redisClient'),
5
+ redisClient: require("./redis/redisClient"),
4
6
 
5
7
  // ✅ Language tranlation Helper
6
- translationHelper: require('./lang/translationHelper'),
8
+ translationHelper: require("./lang/translationHelper"),
7
9
 
8
10
  // ✅ Config
9
- config: require('./config'),
11
+ config: require("./config"),
10
12
 
11
13
  // ✅ Queues
12
- compressionQueue: require('./queue/compressionQueue'),
13
- addCompressionJob: require('./queue/compressionQueue').addCompressionJob,
14
- newsTranslateQueue: require('./queue/newsTranslateQueue'),
15
- addNewsTranslationJob: require('./queue/newsTranslateQueue').addNewsTranslationJob,
16
- addNewsPublishJob: require('./queue/newsPublishQueue').addNewsPublishJob,
17
- addArticlePublishJob: require('./queue/articlePublishQueue').addArticlePublishJob,
18
- addVideoPublishJob: require('./queue/videoPublishQueue').addVideoPublishJob,
19
- addImagePublishJob: require('./queue/imagePublishQueue').addImagePublishJob,
20
- addPagePublishJob: require('./queue/pagePublishQueue').addPagePublishJob,
21
- addProductPublishJob: require('./queue/productPublishQueue').addProductPublishJob,
22
- addQuizPublishJob: require('./queue/quizPublishQueue').addQuizPublishJob,
23
- addPredictionPublishJob: require('./queue/predictionPublishQueue').addPredictionPublishJob,
24
- addImageAlbumPublishJob: require('./queue/imageAlbumPublishQueue').addImageAlbumPublishJob,
25
- };
14
+ compressionQueue: require("./queue/compressionQueue"),
15
+ addCompressionJob: require("./queue/compressionQueue").addCompressionJob,
16
+ newsTranslateQueue: require("./queue/newsTranslateQueue"),
17
+ addNewsTranslationJob: require("./queue/newsTranslateQueue")
18
+ .addNewsTranslationJob,
19
+ addNewsPublishJob: require("./queue/newsPublishQueue").addNewsPublishJob,
20
+ addArticlePublishJob: require("./queue/articlePublishQueue")
21
+ .addArticlePublishJob,
22
+ addVideoPublishJob: require("./queue/videoPublishQueue").addVideoPublishJob,
23
+ addImagePublishJob: require("./queue/imagePublishQueue").addImagePublishJob,
24
+ addPagePublishJob: require("./queue/pagePublishQueue").addPagePublishJob,
25
+ addProductPublishJob: require("./queue/productPublishQueue")
26
+ .addProductPublishJob,
27
+ addQuizPublishJob: require("./queue/quizPublishQueue").addQuizPublishJob,
28
+ addPredictionPublishJob: require("./queue/predictionPublishQueue")
29
+ .addPredictionPublishJob,
30
+ addImageAlbumPublishJob: require("./queue/imageAlbumPublishQueue")
31
+ .addImageAlbumPublishJob,
32
+ addEventPublishJob: require("./queue/eventPublishQueue").addEventPublishJob,
33
+ };
@@ -2,18 +2,18 @@
2
2
  // One-off *publish* scheduler for article items (BullMQ).
3
3
  // Strong upsert: purge any prior job for the same article before adding a new delayed job.
4
4
 
5
- const { Queue } = require('bullmq');
6
- const redisClient = require('../redis/redisClient');
5
+ const { Queue } = require("bullmq");
6
+ const redisClient = require("../redis/redisClient");
7
7
  // const logger = require('../logger'); // uncomment if you have a shared logger
8
8
 
9
- const DEBUG = String(process.env.DEBUG_LOGGER || '').trim() === '1';
9
+ const DEBUG = String(process.env.DEBUG_LOGGER || "").trim() === "1";
10
10
  const DRIFT_MS = Math.max(0, Number(process.env.SCHEDULE_DRIFT_MS || 250));
11
11
 
12
- const articlePublishQueue = new Queue('articlePublishQueue', {
12
+ const articlePublishQueue = new Queue("articlePublishQueue", {
13
13
  connection: redisClient,
14
14
  defaultJobOptions: {
15
15
  attempts: 5,
16
- backoff: { type: 'exponential', delay: 2000 },
16
+ backoff: { type: "exponential", delay: 2000 },
17
17
  removeOnComplete: 500,
18
18
  removeOnFail: 500,
19
19
  },
@@ -21,11 +21,19 @@ const articlePublishQueue = new Queue('articlePublishQueue', {
21
21
 
22
22
  // --- ID helpers --------------------------------------------------------------
23
23
 
24
+ /** Replace invalid chars for BullMQ IDs */
25
+ function sanitizeId(str) {
26
+ return str.replace(/[:.]/g, "-");
27
+ }
28
+
29
+ /** Stable id (latest schedule) */
24
30
  function stableJobId(article_id) {
25
- return `article:publish:${article_id}`;
31
+ return sanitizeId(`article:publish:${article_id}`);
26
32
  }
33
+
34
+ /** Versioned id (keeps history distinct) */
27
35
  function versionedJobId(article_id, runAtIso) {
28
- return `article:publish:${article_id}:${runAtIso}`;
36
+ return sanitizeId(`article:publish:${article_id}:${runAtIso}`);
29
37
  }
30
38
 
31
39
  // --- Time helpers ------------------------------------------------------------
@@ -33,19 +41,21 @@ function versionedJobId(article_id, runAtIso) {
33
41
  /** Parse anything into a UTC Date. Strings without timezone are treated as UTC. */
34
42
  function normalizeToUtcDate(input) {
35
43
  if (input instanceof Date) return new Date(input.getTime());
36
- if (typeof input === 'number') return new Date(input); // epoch ms
44
+ if (typeof input === "number") return new Date(input); // epoch ms
37
45
 
38
- if (typeof input === 'string') {
46
+ if (typeof input === "string") {
39
47
  // Has timezone (Z or ±hh:mm)
40
48
  if (/[zZ]|[+\-]\d{2}:\d{2}$/.test(input)) {
41
49
  const d = new Date(input);
42
- if (Number.isNaN(d.getTime())) throw new Error(`Invalid ISO datetime: ${input}`);
50
+ if (Number.isNaN(d.getTime()))
51
+ throw new Error(`Invalid ISO datetime: ${input}`);
43
52
  return d;
44
53
  }
45
54
  // No timezone => treat as UTC
46
- const s = input.trim().replace(' ', 'T');
55
+ const s = input.trim().replace(" ", "T");
47
56
  const d = new Date(`${s}Z`);
48
- if (Number.isNaN(d.getTime())) throw new Error(`Invalid datetime (no tz): ${input}`);
57
+ if (Number.isNaN(d.getTime()))
58
+ throw new Error(`Invalid datetime (no tz): ${input}`);
49
59
  return d;
50
60
  }
51
61
 
@@ -55,10 +65,12 @@ function normalizeToUtcDate(input) {
55
65
  // --- Purge helpers -----------------------------------------------------------
56
66
 
57
67
  async function findPendingArticleJobs(article_id) {
58
- const states = ['delayed', 'waiting', 'waiting-children', 'active'];
68
+ const states = ["delayed", "waiting", "waiting-children", "active"];
59
69
  const jobs = await articlePublishQueue.getJobs(states);
60
70
  return jobs.filter(
61
- (j) => j?.name === 'article:publish' && j?.data?.article_id === String(article_id)
71
+ (j) =>
72
+ j?.name === "article:publish" &&
73
+ j?.data?.article_id === String(article_id)
62
74
  );
63
75
  }
64
76
 
@@ -72,7 +84,10 @@ async function purgeExistingForArticle(article_id) {
72
84
  await j.remove();
73
85
  removed++;
74
86
  } catch (e) {
75
- if (DEBUG) console.warn(`[articlePublishQueue] failed to remove pending job ${j.id}: ${e.message}`);
87
+ if (DEBUG)
88
+ console.warn(
89
+ `[articlePublishQueue] failed to remove pending job ${j.id}: ${e.message}`
90
+ );
76
91
  }
77
92
  }
78
93
 
@@ -83,12 +98,15 @@ async function purgeExistingForArticle(article_id) {
83
98
  await last.remove();
84
99
  removed++;
85
100
  } catch (e) {
86
- if (DEBUG) console.warn(`[articlePublishQueue] failed to remove stable job ${last.id}: ${e.message}`);
101
+ if (DEBUG)
102
+ console.warn(
103
+ `[articlePublishQueue] failed to remove stable job ${last.id}: ${e.message}`
104
+ );
87
105
  }
88
106
  }
89
107
 
90
108
  if (DEBUG) {
91
- console.info('[articlePublishQueue] purgeExistingForArticle', {
109
+ console.info("[articlePublishQueue] purgeExistingForArticle", {
92
110
  article_id: String(article_id),
93
111
  removed,
94
112
  });
@@ -109,9 +127,16 @@ async function purgeExistingForArticle(article_id) {
109
127
  * @param {object} [params.extra]
110
128
  * @param {boolean} [params.useStableId=false] - if true, uses stable jobId
111
129
  */
112
- async function addArticlePublishJob({ article_id, runAtUtc, extra = {}, useStableId = false }) {
130
+ async function addArticlePublishJob({
131
+ article_id,
132
+ runAtUtc,
133
+ extra = {},
134
+ useStableId = false,
135
+ }) {
113
136
  if (!article_id || !runAtUtc) {
114
- throw new Error('addArticlePublishJob: article_id and runAtUtc are required');
137
+ throw new Error(
138
+ "addArticlePublishJob: article_id and runAtUtc are required"
139
+ );
115
140
  }
116
141
 
117
142
  const runAt = normalizeToUtcDate(runAtUtc);
@@ -126,16 +151,18 @@ async function addArticlePublishJob({ article_id, runAtUtc, extra = {}, useStabl
126
151
  await purgeExistingForArticle(article_id);
127
152
 
128
153
  // Choose jobId strategy
129
- const jobId = useStableId ? stableJobId(article_id) : versionedJobId(article_id, runAtIso);
154
+ const jobId = useStableId
155
+ ? stableJobId(article_id)
156
+ : versionedJobId(article_id, runAtIso);
130
157
 
131
158
  const job = await articlePublishQueue.add(
132
- 'article:publish',
159
+ "article:publish",
133
160
  { article_id: String(article_id), runAtUtc: runAtIso, extra },
134
161
  { jobId, delay: delayMs }
135
162
  );
136
163
 
137
164
  if (DEBUG) {
138
- console.info('[articlePublishQueue] upsert', {
165
+ console.info("[articlePublishQueue] upsert", {
139
166
  article_id: String(article_id),
140
167
  jobId,
141
168
  runAtIso,
@@ -161,4 +188,4 @@ module.exports = {
161
188
  stableJobId,
162
189
  versionedJobId,
163
190
  normalizeToUtcDate,
164
- };
191
+ };
@@ -0,0 +1,187 @@
1
+ // src/queue/eventPublishQueue.js
2
+ // One-off *publish* scheduler for event items (BullMQ).
3
+ // Strong upsert: purge any prior job for the same event before adding a new delayed job.
4
+
5
+ const { Queue } = require("bullmq");
6
+ const redisClient = require("../redis/redisClient");
7
+ // const logger = require('../logger'); // uncomment if you have a shared logger
8
+
9
+ const DEBUG = String(process.env.DEBUG_LOGGER || "").trim() === "1";
10
+ const DRIFT_MS = Math.max(0, Number(process.env.SCHEDULE_DRIFT_MS || 250));
11
+
12
+ const eventPublishQueue = new Queue("eventPublishQueue", {
13
+ connection: redisClient,
14
+ defaultJobOptions: {
15
+ attempts: 5,
16
+ backoff: { type: "exponential", delay: 2000 },
17
+ removeOnComplete: 500,
18
+ removeOnFail: 500,
19
+ },
20
+ });
21
+
22
+ // --- ID helpers --------------------------------------------------------------
23
+
24
+ /** Replace invalid chars for BullMQ IDs */
25
+ function sanitizeId(str) {
26
+ return str.replace(/[:.]/g, "-");
27
+ }
28
+
29
+ /** Stable id (latest schedule) */
30
+ function stableJobId(event_id) {
31
+ return sanitizeId(`event:publish:${event_id}`);
32
+ }
33
+
34
+ /** Versioned id (keeps history distinct) */
35
+ function versionedJobId(event_id, runAtIso) {
36
+ return sanitizeId(`event:publish:${event_id}:${runAtIso}`);
37
+ }
38
+
39
+ // --- Time helpers ------------------------------------------------------------
40
+
41
+ /** Parse anything into a UTC Date. Strings without timezone are treated as UTC. */
42
+ function normalizeToUtcDate(input) {
43
+ if (input instanceof Date) return new Date(input.getTime());
44
+ if (typeof input === "number") return new Date(input); // epoch ms
45
+
46
+ if (typeof input === "string") {
47
+ // Has timezone (Z or ±hh:mm)
48
+ if (/[zZ]|[+\-]\d{2}:\d{2}$/.test(input)) {
49
+ const d = new Date(input);
50
+ if (Number.isNaN(d.getTime()))
51
+ throw new Error(`Invalid ISO datetime: ${input}`);
52
+ return d;
53
+ }
54
+ // No timezone => treat as UTC
55
+ const s = input.trim().replace(" ", "T");
56
+ const d = new Date(`${s}Z`);
57
+ if (Number.isNaN(d.getTime()))
58
+ throw new Error(`Invalid datetime (no tz): ${input}`);
59
+ return d;
60
+ }
61
+
62
+ throw new Error(`Unsupported runAtUtc type: ${typeof input}`);
63
+ }
64
+
65
+ // --- Purge helpers -----------------------------------------------------------
66
+
67
+ async function findPendingEventJobs(event_id) {
68
+ const states = ["delayed", "waiting", "waiting-children", "active"];
69
+ const jobs = await eventPublishQueue.getJobs(states);
70
+ return jobs.filter(
71
+ (j) => j?.name === "event:publish" && j?.data?.event_id === String(event_id)
72
+ );
73
+ }
74
+
75
+ async function purgeExistingForEvent(event_id) {
76
+ let removed = 0;
77
+
78
+ // Remove any pending/waiting/active jobs for this event
79
+ const pendings = await findPendingEventJobs(event_id);
80
+ for (const j of pendings) {
81
+ try {
82
+ await j.remove();
83
+ removed++;
84
+ } catch (e) {
85
+ if (DEBUG)
86
+ console.warn(
87
+ `[eventPublishQueue] failed to remove pending job ${j.id}: ${e.message}`
88
+ );
89
+ }
90
+ }
91
+
92
+ // Also remove a leftover stable job if present
93
+ const last = await eventPublishQueue.getJob(stableJobId(event_id));
94
+ if (last) {
95
+ try {
96
+ await last.remove();
97
+ removed++;
98
+ } catch (e) {
99
+ if (DEBUG)
100
+ console.warn(
101
+ `[eventPublishQueue] failed to remove stable job ${last.id}: ${e.message}`
102
+ );
103
+ }
104
+ }
105
+
106
+ if (DEBUG) {
107
+ console.info("[eventPublishQueue] purgeExistingForEvent", {
108
+ event_id: String(event_id),
109
+ removed,
110
+ });
111
+ }
112
+ return removed;
113
+ }
114
+
115
+ // --- Public API --------------------------------------------------------------
116
+
117
+ /**
118
+ * Upsert a one-off publish job for an event.
119
+ * - Purges any previous jobs for this event
120
+ * - Uses a versioned jobId by default (or stable if useStableId=true)
121
+ *
122
+ * @param {Object} params
123
+ * @param {string|number} params.event_id
124
+ * @param {string|number|Date} params.runAtUtc - ISO string, epoch ms, or Date (UTC)
125
+ * @param {object} [params.extra]
126
+ * @param {boolean} [params.useStableId=false] - if true, uses stable jobId
127
+ */
128
+ async function addEventPublishJob({
129
+ event_id,
130
+ runAtUtc,
131
+ extra = {},
132
+ useStableId = false,
133
+ }) {
134
+ if (!event_id || !runAtUtc) {
135
+ throw new Error("addEventPublishJob: event_id and runAtUtc are required");
136
+ }
137
+
138
+ const runAt = normalizeToUtcDate(runAtUtc);
139
+ const runAtIso = runAt.toISOString();
140
+
141
+ // drift guard to avoid immediate fire due to ms skew
142
+ const now = Date.now();
143
+ let delayMs = runAt.getTime() - now;
144
+ if (delayMs < DRIFT_MS) delayMs = Math.max(0, DRIFT_MS);
145
+
146
+ // Purge any prior jobs for this event (across states)
147
+ await purgeExistingForEvent(event_id);
148
+
149
+ // Choose jobId strategy
150
+ const jobId = useStableId
151
+ ? stableJobId(event_id)
152
+ : versionedJobId(event_id, runAtIso);
153
+
154
+ const job = await eventPublishQueue.add(
155
+ "event:publish",
156
+ { event_id: String(event_id), runAtUtc: runAtIso, extra },
157
+ { jobId, delay: delayMs }
158
+ );
159
+
160
+ if (DEBUG) {
161
+ console.info("[eventPublishQueue] upsert", {
162
+ event_id: String(event_id),
163
+ jobId,
164
+ runAtIso,
165
+ delayMs,
166
+ nowIso: new Date(now).toISOString(),
167
+ });
168
+ }
169
+
170
+ return job;
171
+ }
172
+
173
+ /** Cancel any scheduled publish for a given event (if present). */
174
+ async function cancelEventPublishJob(event_id) {
175
+ const removed = await purgeExistingForEvent(event_id);
176
+ return removed > 0;
177
+ }
178
+
179
+ module.exports = {
180
+ eventPublishQueue,
181
+ addEventPublishJob,
182
+ cancelEventPublishJob,
183
+ // exported helpers for tooling/tests
184
+ stableJobId,
185
+ versionedJobId,
186
+ normalizeToUtcDate,
187
+ };
@@ -2,18 +2,18 @@
2
2
  // One-off *publish* scheduler for image album items (BullMQ).
3
3
  // Strong upsert: purge any prior job for the same album before adding a new delayed job.
4
4
 
5
- const { Queue } = require('bullmq');
6
- const redisClient = require('../redis/redisClient');
5
+ const { Queue } = require("bullmq");
6
+ const redisClient = require("../redis/redisClient");
7
7
  // const logger = require('../logger'); // uncomment if you have a shared logger
8
8
 
9
- const DEBUG = String(process.env.DEBUG_LOGGER || '').trim() === '1';
9
+ const DEBUG = String(process.env.DEBUG_LOGGER || "").trim() === "1";
10
10
  const DRIFT_MS = Math.max(0, Number(process.env.SCHEDULE_DRIFT_MS || 250));
11
11
 
12
- const imageAlbumPublishQueue = new Queue('imageAlbumPublishQueue', {
12
+ const imageAlbumPublishQueue = new Queue("imageAlbumPublishQueue", {
13
13
  connection: redisClient,
14
14
  defaultJobOptions: {
15
15
  attempts: 5,
16
- backoff: { type: 'exponential', delay: 2000 },
16
+ backoff: { type: "exponential", delay: 2000 },
17
17
  removeOnComplete: 500,
18
18
  removeOnFail: 500,
19
19
  },
@@ -21,11 +21,19 @@ const imageAlbumPublishQueue = new Queue('imageAlbumPublishQueue', {
21
21
 
22
22
  // ── ID helpers ────────────────────────────────────────────────────────────────
23
23
 
24
+ /** Replace invalid chars for BullMQ IDs */
25
+ function sanitizeId(str) {
26
+ return str.replace(/[:.]/g, "-");
27
+ }
28
+
29
+ /** Stable id (latest schedule) */
24
30
  function stableJobId(album_id) {
25
- return `imageAlbum:publish:${album_id}`;
31
+ return sanitizeId(`imageAlbum:publish:${album_id}`);
26
32
  }
33
+
34
+ /** Versioned id (keeps history distinct) */
27
35
  function versionedJobId(album_id, runAtIso) {
28
- return `imageAlbum:publish:${album_id}:${runAtIso}`;
36
+ return sanitizeId(`imageAlbum:publish:${album_id}:${runAtIso}`);
29
37
  }
30
38
 
31
39
  // ── Time helpers ──────────────────────────────────────────────────────────────
@@ -33,19 +41,21 @@ function versionedJobId(album_id, runAtIso) {
33
41
  /** Parse anything into a UTC Date. Strings without timezone are treated as UTC. */
34
42
  function normalizeToUtcDate(input) {
35
43
  if (input instanceof Date) return new Date(input.getTime());
36
- if (typeof input === 'number') return new Date(input); // epoch ms
44
+ if (typeof input === "number") return new Date(input); // epoch ms
37
45
 
38
- if (typeof input === 'string') {
46
+ if (typeof input === "string") {
39
47
  // Has timezone (Z or ±hh:mm)
40
48
  if (/[zZ]|[+\-]\d{2}:\d{2}$/.test(input)) {
41
49
  const d = new Date(input);
42
- if (Number.isNaN(d.getTime())) throw new Error(`Invalid ISO datetime: ${input}`);
50
+ if (Number.isNaN(d.getTime()))
51
+ throw new Error(`Invalid ISO datetime: ${input}`);
43
52
  return d;
44
53
  }
45
54
  // No timezone => treat as UTC
46
- const s = input.trim().replace(' ', 'T');
55
+ const s = input.trim().replace(" ", "T");
47
56
  const d = new Date(`${s}Z`);
48
- if (Number.isNaN(d.getTime())) throw new Error(`Invalid datetime (no tz): ${input}`);
57
+ if (Number.isNaN(d.getTime()))
58
+ throw new Error(`Invalid datetime (no tz): ${input}`);
49
59
  return d;
50
60
  }
51
61
 
@@ -55,10 +65,11 @@ function normalizeToUtcDate(input) {
55
65
  // ── Purge helpers ────────────────────────────────────────────────────────────
56
66
 
57
67
  async function findPendingAlbumJobs(album_id) {
58
- const states = ['delayed', 'waiting', 'waiting-children', 'active'];
68
+ const states = ["delayed", "waiting", "waiting-children", "active"];
59
69
  const jobs = await imageAlbumPublishQueue.getJobs(states);
60
70
  return jobs.filter(
61
- (j) => j?.name === 'imageAlbum:publish' && j?.data?.album_id === String(album_id)
71
+ (j) =>
72
+ j?.name === "imageAlbum:publish" && j?.data?.album_id === String(album_id)
62
73
  );
63
74
  }
64
75
 
@@ -72,7 +83,10 @@ async function purgeExistingForAlbum(album_id) {
72
83
  await j.remove();
73
84
  removed++;
74
85
  } catch (e) {
75
- if (DEBUG) console.warn(`[imageAlbumPublishQueue] failed to remove pending job ${j.id}: ${e.message}`);
86
+ if (DEBUG)
87
+ console.warn(
88
+ `[imageAlbumPublishQueue] failed to remove pending job ${j.id}: ${e.message}`
89
+ );
76
90
  }
77
91
  }
78
92
 
@@ -83,12 +97,15 @@ async function purgeExistingForAlbum(album_id) {
83
97
  await last.remove();
84
98
  removed++;
85
99
  } catch (e) {
86
- if (DEBUG) console.warn(`[imageAlbumPublishQueue] failed to remove stable job ${last.id}: ${e.message}`);
100
+ if (DEBUG)
101
+ console.warn(
102
+ `[imageAlbumPublishQueue] failed to remove stable job ${last.id}: ${e.message}`
103
+ );
87
104
  }
88
105
  }
89
106
 
90
107
  if (DEBUG) {
91
- console.info('[imageAlbumPublishQueue] purgeExistingForAlbum', {
108
+ console.info("[imageAlbumPublishQueue] purgeExistingForAlbum", {
92
109
  album_id: String(album_id),
93
110
  removed,
94
111
  });
@@ -109,9 +126,16 @@ async function purgeExistingForAlbum(album_id) {
109
126
  * @param {object} [params.extra]
110
127
  * @param {boolean} [params.useStableId=false] - if true, uses stable jobId
111
128
  */
112
- async function addImageAlbumPublishJob({ album_id, runAtUtc, extra = {}, useStableId = false }) {
129
+ async function addImageAlbumPublishJob({
130
+ album_id,
131
+ runAtUtc,
132
+ extra = {},
133
+ useStableId = false,
134
+ }) {
113
135
  if (!album_id || !runAtUtc) {
114
- throw new Error('addImageAlbumPublishJob: album_id and runAtUtc are required');
136
+ throw new Error(
137
+ "addImageAlbumPublishJob: album_id and runAtUtc are required"
138
+ );
115
139
  }
116
140
 
117
141
  const runAt = normalizeToUtcDate(runAtUtc);
@@ -126,16 +150,18 @@ async function addImageAlbumPublishJob({ album_id, runAtUtc, extra = {}, useStab
126
150
  await purgeExistingForAlbum(album_id);
127
151
 
128
152
  // Choose jobId strategy
129
- const jobId = useStableId ? stableJobId(album_id) : versionedJobId(album_id, runAtIso);
153
+ const jobId = useStableId
154
+ ? stableJobId(album_id)
155
+ : versionedJobId(album_id, runAtIso);
130
156
 
131
157
  const job = await imageAlbumPublishQueue.add(
132
- 'imageAlbum:publish',
158
+ "imageAlbum:publish",
133
159
  { album_id: String(album_id), runAtUtc: runAtIso, extra },
134
160
  { jobId, delay: delayMs }
135
161
  );
136
162
 
137
163
  if (DEBUG) {
138
- console.info('[imageAlbumPublishQueue] upsert', {
164
+ console.info("[imageAlbumPublishQueue] upsert", {
139
165
  album_id: String(album_id),
140
166
  jobId,
141
167
  runAtIso,
@@ -161,4 +187,4 @@ module.exports = {
161
187
  stableJobId,
162
188
  versionedJobId,
163
189
  normalizeToUtcDate,
164
- };
190
+ };