@cronwatch/sdk 0.5.1 → 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.js CHANGED
@@ -155,15 +155,15 @@ function runCovers(startedAt, dueAt, followingAt = null) {
155
155
  }
156
156
  function inSpringForwardGap(parsed, startedAt, fireAt) {
157
157
  const LOOKBACK = 3 * 36e5;
158
- const after = utcOffset(fireAt, parsed.timezone);
158
+ const after2 = utcOffset(fireAt, parsed.timezone);
159
159
  const before = utcOffset(fireAt - LOOKBACK, parsed.timezone);
160
- const gap = after - before;
160
+ const gap = after2 - before;
161
161
  if (gap <= 0) return false;
162
162
  let lo = fireAt - LOOKBACK;
163
163
  let hi = fireAt;
164
164
  while (hi - lo > 6e4) {
165
165
  const mid = lo + Math.floor((hi - lo) / 2);
166
- if (utcOffset(mid, parsed.timezone) === after) hi = mid;
166
+ if (utcOffset(mid, parsed.timezone) === after2) hi = mid;
167
167
  else lo = mid;
168
168
  }
169
169
  const jumpAt = Math.floor(hi / 6e4) * 6e4;
@@ -501,8 +501,8 @@ ${out}`);
501
501
  break;
502
502
  }
503
503
  title = `${name} recovered`;
504
- const after = draft.details.after.map((c) => c.replace("_", " ")).join(", ");
505
- lines.push(`A run ${run ? when(run.startedAt, now) : "just now"} succeeded${after ? ` after: ${after}` : ""}.`);
504
+ const after2 = draft.details.after.map((c) => c.replace("_", " ")).join(", ");
505
+ lines.push(`A run ${run ? when(run.startedAt, now) : "just now"} succeeded${after2 ? ` after: ${after2}` : ""}.`);
506
506
  if (run?.durationMs !== null && run?.durationMs !== void 0) lines.push(`Ran ${formatDuration(run.durationMs)}.`);
507
507
  break;
508
508
  }
@@ -689,6 +689,9 @@ function checkExpectation(expect, output) {
689
689
  function escapeHtml(value) {
690
690
  return String(value ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
691
691
  }
692
+ function escapeName(value) {
693
+ return escapeHtml(value).replace(/([_:./-]+)(?=[^_:./-])/g, "$1<wbr>");
694
+ }
692
695
 
693
696
  // src/routes/icons.ts
694
697
  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>';
@@ -1035,7 +1038,7 @@ function dayTimeline(lanes, span, base, total) {
1035
1038
  const { job } = input;
1036
1039
  const parts = lane(input, span, { nowInLane: false, label: job.name });
1037
1040
  return {
1038
- 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>`,
1041
+ 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>`,
1039
1042
  words: `<li>${escapeHtml(`${job.name} (${job.definition.schedule ?? "no schedule"}): ${parts.words}.`)}</li>`
1040
1043
  };
1041
1044
  });
@@ -1275,7 +1278,7 @@ ${options.refresh ? `<meta http-equiv="refresh" content="${options.refresh}">` :
1275
1278
  }
1276
1279
  function brand(base, crumb) {
1277
1280
  const home = `<a href="${h(base)}/">${MARK}<span>CronWatch</span></a>`;
1278
- 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>`;
1281
+ 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>`;
1279
1282
  }
1280
1283
  var HEALTH = {
1281
1284
  failing: ["bad", "failing"],
@@ -1328,7 +1331,7 @@ function dashboardPage(jobs, runsByJob, now, base, checkedAt, lanes = jobs.slice
1328
1331
  const last = job.lastRun;
1329
1332
  const d = job.definition;
1330
1333
  return `<tr>
1331
- <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>
1334
+ <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>
1332
1335
  <td class="health">${healthState(job)}</td>
1333
1336
  <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>
1334
1337
  <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>
@@ -1400,7 +1403,7 @@ function jobPage(job, runs, now, base, complete = true) {
1400
1403
  <section class="sec intro" aria-label="Job">
1401
1404
  <h2>Job</h2>
1402
1405
  <div>
1403
- <h1 class="jobname">${h(job.name)}</h1>
1406
+ <h1 class="jobname">${escapeName(job.name)}</h1>
1404
1407
  ${d.description ? `<p class="desc">${h(d.description)}</p>` : ""}
1405
1408
  <p class="stateline">${healthState(job)}${why ? `<span class="why">${h(why)}</span>` : ""}</p>
1406
1409
  <div class="actions">
@@ -2146,8 +2149,7 @@ var CronWatch = class {
2146
2149
  const started = recorded ? this.updateState(name, (before) => ({ state: onRunStart(before), result: void 0 })).then(() => {
2147
2150
  }, (e) => this.onError(e, `starting ${name}`)) : Promise.resolve();
2148
2151
  const recorder = createRecorder(run);
2149
- const timer = setTimeout(() => recorder.abort(), timeoutMs(definition));
2150
- if (typeof timer === "object" && "unref" in timer) timer.unref();
2152
+ const clearTimer = after(timeoutMs(definition), () => recorder.abort());
2151
2153
  let result;
2152
2154
  let error;
2153
2155
  let threw = false;
@@ -2157,7 +2159,7 @@ var CronWatch = class {
2157
2159
  error = e;
2158
2160
  threw = true;
2159
2161
  } finally {
2160
- clearTimeout(timer);
2162
+ clearTimer();
2161
2163
  }
2162
2164
  const finishedAt = this.now();
2163
2165
  run.finishedAt = finishedAt;
@@ -2712,7 +2714,7 @@ ${id}`;
2712
2714
  */
2713
2715
  start(every = "1m") {
2714
2716
  if (this.timer) return;
2715
- const ms = Math.max(5e3, parseDuration(every, "check interval"));
2717
+ const ms = Math.min(TIMER_MAX_MS, Math.max(5e3, parseDuration(every, "check interval")));
2716
2718
  if (this.deferDelivery && !this.warnedDeferredStart) {
2717
2719
  this.warnedDeferredStart = true;
2718
2720
  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.');
@@ -2851,20 +2853,30 @@ function checkRunId(job, id, method) {
2851
2853
  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`);
2852
2854
  }
2853
2855
  }
2854
- function joinLines(before, after) {
2855
- if (before === null || before === "") return after;
2856
- if (after === null) return before;
2856
+ function joinLines(before, after2) {
2857
+ if (before === null || before === "") return after2;
2858
+ if (after2 === null) return before;
2857
2859
  return `${before}
2858
- ${after}`;
2860
+ ${after2}`;
2859
2861
  }
2860
- function joinOutput(before, after) {
2861
- const joined = joinLines(before, after);
2862
+ function joinOutput(before, after2) {
2863
+ const joined = joinLines(before, after2);
2862
2864
  return joined === null ? null : capOutput(joined);
2863
2865
  }
2864
2866
  function clampLimit(limit, fallback, min) {
2865
2867
  const n = typeof limit === "number" && Number.isFinite(limit) ? Math.trunc(limit) : fallback;
2866
2868
  return Math.min(500, Math.max(min, n));
2867
2869
  }
2870
+ var TIMER_MAX_MS = 2 ** 31 - 1;
2871
+ function after(ms, fn) {
2872
+ let timer;
2873
+ const arm = (left) => {
2874
+ timer = left > TIMER_MAX_MS ? setTimeout(() => arm(left - TIMER_MAX_MS), TIMER_MAX_MS) : setTimeout(fn, left);
2875
+ if (typeof timer === "object" && "unref" in timer) timer.unref();
2876
+ };
2877
+ arm(ms);
2878
+ return () => clearTimeout(timer);
2879
+ }
2868
2880
  function withTimeout(promise, ms) {
2869
2881
  return new Promise((resolve, reject) => {
2870
2882
  const t = setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms);