@yellowpanther/shared 1.2.5 → 1.2.7

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.
@@ -2,18 +2,18 @@
2
2
  // One-off *publish* scheduler for video items (BullMQ).
3
3
  // Strong upsert: purge any prior job for the same video 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 videoPublishQueue = new Queue('videoPublishQueue', {
12
+ const videoPublishQueue = new Queue("videoPublishQueue", {
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 videoPublishQueue = new Queue('videoPublishQueue', {
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(video_id) {
25
- return `video:publish:${video_id}`;
31
+ return sanitizeId(`video:publish:${video_id}`);
26
32
  }
33
+
34
+ /** Versioned id (keeps history distinct) */
27
35
  function versionedJobId(video_id, runAtIso) {
28
- return `video:publish:${video_id}:${runAtIso}`;
36
+ return sanitizeId(`video:publish:${video_id}:${runAtIso}`);
29
37
  }
30
38
 
31
39
  // ── Time helpers ───────────────────────────────────────────────────────────────
@@ -33,19 +41,21 @@ function versionedJobId(video_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,10 @@ function normalizeToUtcDate(input) {
55
65
  // ── Purge helpers ─────────────────────────────────────────────────────────────
56
66
 
57
67
  async function findPendingVideoJobs(video_id) {
58
- const states = ['delayed', 'waiting', 'waiting-children', 'active'];
68
+ const states = ["delayed", "waiting", "waiting-children", "active"];
59
69
  const jobs = await videoPublishQueue.getJobs(states);
60
70
  return jobs.filter(
61
- (j) => j?.name === 'video:publish' && j?.data?.video_id === String(video_id)
71
+ (j) => j?.name === "video:publish" && j?.data?.video_id === String(video_id)
62
72
  );
63
73
  }
64
74
 
@@ -72,7 +82,10 @@ async function purgeExistingForVideo(video_id) {
72
82
  await j.remove();
73
83
  removed++;
74
84
  } catch (e) {
75
- if (DEBUG) console.warn(`[videoPublishQueue] failed to remove pending job ${j.id}: ${e.message}`);
85
+ if (DEBUG)
86
+ console.warn(
87
+ `[videoPublishQueue] failed to remove pending job ${j.id}: ${e.message}`
88
+ );
76
89
  }
77
90
  }
78
91
 
@@ -83,12 +96,15 @@ async function purgeExistingForVideo(video_id) {
83
96
  await last.remove();
84
97
  removed++;
85
98
  } catch (e) {
86
- if (DEBUG) console.warn(`[videoPublishQueue] failed to remove stable job ${last.id}: ${e.message}`);
99
+ if (DEBUG)
100
+ console.warn(
101
+ `[videoPublishQueue] failed to remove stable job ${last.id}: ${e.message}`
102
+ );
87
103
  }
88
104
  }
89
105
 
90
106
  if (DEBUG) {
91
- console.info('[videoPublishQueue] purgeExistingForVideo', {
107
+ console.info("[videoPublishQueue] purgeExistingForVideo", {
92
108
  video_id: String(video_id),
93
109
  removed,
94
110
  });
@@ -109,9 +125,14 @@ async function purgeExistingForVideo(video_id) {
109
125
  * @param {object} [params.extra]
110
126
  * @param {boolean} [params.useStableId=false] - if true, uses stable jobId
111
127
  */
112
- async function addVideoPublishJob({ video_id, runAtUtc, extra = {}, useStableId = false }) {
128
+ async function addVideoPublishJob({
129
+ video_id,
130
+ runAtUtc,
131
+ extra = {},
132
+ useStableId = false,
133
+ }) {
113
134
  if (!video_id || !runAtUtc) {
114
- throw new Error('addVideoPublishJob: video_id and runAtUtc are required');
135
+ throw new Error("addVideoPublishJob: video_id and runAtUtc are required");
115
136
  }
116
137
 
117
138
  const runAt = normalizeToUtcDate(runAtUtc);
@@ -126,16 +147,18 @@ async function addVideoPublishJob({ video_id, runAtUtc, extra = {}, useStableId
126
147
  await purgeExistingForVideo(video_id);
127
148
 
128
149
  // Choose jobId strategy
129
- const jobId = useStableId ? stableJobId(video_id) : versionedJobId(video_id, runAtIso);
150
+ const jobId = useStableId
151
+ ? stableJobId(video_id)
152
+ : versionedJobId(video_id, runAtIso);
130
153
 
131
154
  const job = await videoPublishQueue.add(
132
- 'video:publish',
155
+ "video:publish",
133
156
  { video_id: String(video_id), runAtUtc: runAtIso, extra },
134
157
  { jobId, delay: delayMs }
135
158
  );
136
159
 
137
160
  if (DEBUG) {
138
- console.info('[videoPublishQueue] upsert', {
161
+ console.info("[videoPublishQueue] upsert", {
139
162
  video_id: String(video_id),
140
163
  jobId,
141
164
  runAtIso,
@@ -161,4 +184,4 @@ module.exports = {
161
184
  stableJobId,
162
185
  versionedJobId,
163
186
  normalizeToUtcDate,
164
- };
187
+ };