@cronwatch/sdk 0.6.0 → 0.6.1

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/dist/index.cjs CHANGED
@@ -157,15 +157,15 @@ function runCovers(startedAt, dueAt, followingAt = null) {
157
157
  }
158
158
  function inSpringForwardGap(parsed, startedAt, fireAt) {
159
159
  const LOOKBACK = 3 * 36e5;
160
- const after = utcOffset(fireAt, parsed.timezone);
160
+ const after2 = utcOffset(fireAt, parsed.timezone);
161
161
  const before = utcOffset(fireAt - LOOKBACK, parsed.timezone);
162
- const gap = after - before;
162
+ const gap = after2 - before;
163
163
  if (gap <= 0) return false;
164
164
  let lo = fireAt - LOOKBACK;
165
165
  let hi = fireAt;
166
166
  while (hi - lo > 6e4) {
167
167
  const mid = lo + Math.floor((hi - lo) / 2);
168
- if (utcOffset(mid, parsed.timezone) === after) hi = mid;
168
+ if (utcOffset(mid, parsed.timezone) === after2) hi = mid;
169
169
  else lo = mid;
170
170
  }
171
171
  const jumpAt = Math.floor(hi / 6e4) * 6e4;
@@ -503,8 +503,8 @@ ${out}`);
503
503
  break;
504
504
  }
505
505
  title = `${name} recovered`;
506
- const after = draft.details.after.map((c) => c.replace("_", " ")).join(", ");
507
- lines.push(`A run ${run ? when(run.startedAt, now) : "just now"} succeeded${after ? ` after: ${after}` : ""}.`);
506
+ const after2 = draft.details.after.map((c) => c.replace("_", " ")).join(", ");
507
+ lines.push(`A run ${run ? when(run.startedAt, now) : "just now"} succeeded${after2 ? ` after: ${after2}` : ""}.`);
508
508
  if (run?.durationMs !== null && run?.durationMs !== void 0) lines.push(`Ran ${formatDuration(run.durationMs)}.`);
509
509
  break;
510
510
  }
@@ -691,6 +691,9 @@ function checkExpectation(expect, output) {
691
691
  function escapeHtml(value) {
692
692
  return String(value ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
693
693
  }
694
+ function escapeName(value) {
695
+ return escapeHtml(value).replace(/([_:./-]+)(?=[^_:./-])/g, "$1<wbr>");
696
+ }
694
697
 
695
698
  // src/routes/icons.ts
696
699
  var ICON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><rect width="40" height="40" rx="10" fill="#141417"/><rect x="0.5" y="0.5" width="39" height="39" rx="9.5" fill="none" stroke="#2a2a2f"/><circle cx="20" cy="20" r="10.5" fill="none" stroke="#f3f3f1" stroke-width="2"/><path d="M20 12.5V20h6" fill="none" stroke="#f3f3f1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
@@ -1037,7 +1040,7 @@ function dayTimeline(lanes, span, base, total) {
1037
1040
  const { job } = input;
1038
1041
  const parts = lane(input, span, { nowInLane: false, label: job.name });
1039
1042
  return {
1040
- html: `<li class="lane"><div class="who"><i class="sq ${stateClass(job)}" aria-hidden="true"></i><a class="name" href="${escapeHtml(base)}/jobs/${encodeURIComponent(job.name)}">${escapeHtml(job.name)}</a><span class="sched">${escapeHtml(job.definition.schedule ?? "no schedule")}</span></div><div class="track">${parts.svg}${parts.note}</div></li>`,
1043
+ html: `<li class="lane"><div class="who"><i class="sq ${stateClass(job)}" aria-hidden="true"></i><a class="name" href="${escapeHtml(base)}/jobs/${encodeURIComponent(job.name)}">${escapeName(job.name)}</a><span class="sched">${escapeHtml(job.definition.schedule ?? "no schedule")}</span></div><div class="track">${parts.svg}${parts.note}</div></li>`,
1041
1044
  words: `<li>${escapeHtml(`${job.name} (${job.definition.schedule ?? "no schedule"}): ${parts.words}.`)}</li>`
1042
1045
  };
1043
1046
  });
@@ -1277,7 +1280,7 @@ ${options.refresh ? `<meta http-equiv="refresh" content="${options.refresh}">` :
1277
1280
  }
1278
1281
  function brand(base, crumb) {
1279
1282
  const home = `<a href="${h(base)}/">${MARK}<span>CronWatch</span></a>`;
1280
- return crumb === void 0 ? `<p class="brand">${home}</p>` : `<p class="brand">${home}<span class="slash" aria-hidden="true">/</span><span class="crumb">${h(crumb)}</span></p>`;
1283
+ return crumb === void 0 ? `<p class="brand">${home}</p>` : `<p class="brand">${home}<span class="slash" aria-hidden="true">/</span><span class="crumb">${escapeName(crumb)}</span></p>`;
1281
1284
  }
1282
1285
  var HEALTH = {
1283
1286
  failing: ["bad", "failing"],
@@ -1330,7 +1333,7 @@ function dashboardPage(jobs, runsByJob, now, base, checkedAt, lanes = jobs.slice
1330
1333
  const last = job.lastRun;
1331
1334
  const d = job.definition;
1332
1335
  return `<tr>
1333
- <td class="job"><a class="name" href="${h(base)}/jobs/${encodeURIComponent(job.name)}">${h(job.name)}</a>${d.description ? `<span class="desc">${h(d.description)}</span>` : ""}</td>
1336
+ <td class="job"><a class="name" href="${h(base)}/jobs/${encodeURIComponent(job.name)}">${escapeName(job.name)}</a>${d.description ? `<span class="desc">${h(d.description)}</span>` : ""}</td>
1334
1337
  <td class="health">${healthState(job)}</td>
1335
1338
  <td class="nowrap hide-sm">${d.schedule ? `${h(d.schedule)}${d.timezone ? `<span class="tz">${h(d.timezone)}</span>` : ""}` : `<span class="muted">no schedule</span>`}</td>
1336
1339
  <td class="nowrap last">${last ? `${runState(last)} ${stamp(last.startedAt, now)}${last.durationMs !== null ? `<span class="sub">took ${h(formatDuration(last.durationMs))}</span>` : ""}` : `<span class="muted">never</span>`}</td>
@@ -1402,7 +1405,7 @@ function jobPage(job, runs, now, base, complete = true) {
1402
1405
  <section class="sec intro" aria-label="Job">
1403
1406
  <h2>Job</h2>
1404
1407
  <div>
1405
- <h1 class="jobname">${h(job.name)}</h1>
1408
+ <h1 class="jobname">${escapeName(job.name)}</h1>
1406
1409
  ${d.description ? `<p class="desc">${h(d.description)}</p>` : ""}
1407
1410
  <p class="stateline">${healthState(job)}${why ? `<span class="why">${h(why)}</span>` : ""}</p>
1408
1411
  <div class="actions">
@@ -2148,8 +2151,7 @@ var CronWatch = class {
2148
2151
  const started = recorded ? this.updateState(name, (before) => ({ state: onRunStart(before), result: void 0 })).then(() => {
2149
2152
  }, (e) => this.onError(e, `starting ${name}`)) : Promise.resolve();
2150
2153
  const recorder = createRecorder(run);
2151
- const timer = setTimeout(() => recorder.abort(), timeoutMs(definition));
2152
- if (typeof timer === "object" && "unref" in timer) timer.unref();
2154
+ const clearTimer = after(timeoutMs(definition), () => recorder.abort());
2153
2155
  let result;
2154
2156
  let error;
2155
2157
  let threw = false;
@@ -2159,7 +2161,7 @@ var CronWatch = class {
2159
2161
  error = e;
2160
2162
  threw = true;
2161
2163
  } finally {
2162
- clearTimeout(timer);
2164
+ clearTimer();
2163
2165
  }
2164
2166
  const finishedAt = this.now();
2165
2167
  run.finishedAt = finishedAt;
@@ -2714,7 +2716,7 @@ ${id}`;
2714
2716
  */
2715
2717
  start(every = "1m") {
2716
2718
  if (this.timer) return;
2717
- const ms = Math.max(5e3, parseDuration(every, "check interval"));
2719
+ const ms = Math.min(TIMER_MAX_MS, Math.max(5e3, parseDuration(every, "check interval")));
2718
2720
  if (this.deferDelivery && !this.warnedDeferredStart) {
2719
2721
  this.warnedDeferredStart = true;
2720
2722
  console.warn('[cronwatch] start() was called with deliver: "check", so these checks send no alerts. Another process must run checks with deliver: "now" (the default) to send them.');
@@ -2853,20 +2855,30 @@ function checkRunId(job, id, method) {
2853
2855
  throw new Error(`job "${job}": ${method}() cannot take a run id starting with "${RESERVED_RUN_ID_PREFIX}", which the pg_cron source uses for its runs`);
2854
2856
  }
2855
2857
  }
2856
- function joinLines(before, after) {
2857
- if (before === null || before === "") return after;
2858
- if (after === null) return before;
2858
+ function joinLines(before, after2) {
2859
+ if (before === null || before === "") return after2;
2860
+ if (after2 === null) return before;
2859
2861
  return `${before}
2860
- ${after}`;
2862
+ ${after2}`;
2861
2863
  }
2862
- function joinOutput(before, after) {
2863
- const joined = joinLines(before, after);
2864
+ function joinOutput(before, after2) {
2865
+ const joined = joinLines(before, after2);
2864
2866
  return joined === null ? null : capOutput(joined);
2865
2867
  }
2866
2868
  function clampLimit(limit, fallback, min) {
2867
2869
  const n = typeof limit === "number" && Number.isFinite(limit) ? Math.trunc(limit) : fallback;
2868
2870
  return Math.min(500, Math.max(min, n));
2869
2871
  }
2872
+ var TIMER_MAX_MS = 2 ** 31 - 1;
2873
+ function after(ms, fn) {
2874
+ let timer;
2875
+ const arm = (left) => {
2876
+ timer = left > TIMER_MAX_MS ? setTimeout(() => arm(left - TIMER_MAX_MS), TIMER_MAX_MS) : setTimeout(fn, left);
2877
+ if (typeof timer === "object" && "unref" in timer) timer.unref();
2878
+ };
2879
+ arm(ms);
2880
+ return () => clearTimeout(timer);
2881
+ }
2870
2882
  function withTimeout(promise, ms) {
2871
2883
  return new Promise((resolve, reject) => {
2872
2884
  const t = setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms);