@cronwatch/sdk 0.7.0 → 0.8.0

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.
Files changed (63) hide show
  1. package/dist/anthropic.cjs +13 -2
  2. package/dist/anthropic.cjs.map +1 -1
  3. package/dist/anthropic.js +13 -2
  4. package/dist/anthropic.js.map +1 -1
  5. package/dist/bugsnag.cjs +8 -1
  6. package/dist/bugsnag.cjs.map +1 -1
  7. package/dist/bugsnag.js +8 -1
  8. package/dist/bugsnag.js.map +1 -1
  9. package/dist/d1.cjs +4 -1
  10. package/dist/d1.cjs.map +1 -1
  11. package/dist/d1.js +4 -1
  12. package/dist/d1.js.map +1 -1
  13. package/dist/datadog.cjs.map +1 -1
  14. package/dist/datadog.js.map +1 -1
  15. package/dist/discord.cjs.map +1 -1
  16. package/dist/discord.js.map +1 -1
  17. package/dist/honeybadger.cjs +8 -1
  18. package/dist/honeybadger.cjs.map +1 -1
  19. package/dist/honeybadger.js +8 -1
  20. package/dist/honeybadger.js.map +1 -1
  21. package/dist/index.cjs +69 -20
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.js +69 -20
  24. package/dist/index.js.map +1 -1
  25. package/dist/mailgun.cjs.map +1 -1
  26. package/dist/mailgun.js.map +1 -1
  27. package/dist/newrelic.cjs.map +1 -1
  28. package/dist/newrelic.js.map +1 -1
  29. package/dist/pg-cron.cjs +8 -1
  30. package/dist/pg-cron.cjs.map +1 -1
  31. package/dist/pg-cron.js +8 -1
  32. package/dist/pg-cron.js.map +1 -1
  33. package/dist/postgres.cjs +6 -1
  34. package/dist/postgres.cjs.map +1 -1
  35. package/dist/postgres.js +6 -1
  36. package/dist/postgres.js.map +1 -1
  37. package/dist/postmark.cjs.map +1 -1
  38. package/dist/postmark.js.map +1 -1
  39. package/dist/resend.cjs.map +1 -1
  40. package/dist/resend.js.map +1 -1
  41. package/dist/rollbar.cjs +8 -1
  42. package/dist/rollbar.cjs.map +1 -1
  43. package/dist/rollbar.js +8 -1
  44. package/dist/rollbar.js.map +1 -1
  45. package/dist/sendgrid.cjs.map +1 -1
  46. package/dist/sendgrid.js.map +1 -1
  47. package/dist/sentry.cjs +8 -1
  48. package/dist/sentry.cjs.map +1 -1
  49. package/dist/sentry.js +8 -1
  50. package/dist/sentry.js.map +1 -1
  51. package/dist/ses.cjs.map +1 -1
  52. package/dist/ses.js.map +1 -1
  53. package/dist/slack.cjs.map +1 -1
  54. package/dist/slack.js.map +1 -1
  55. package/dist/sqlite.cjs +4 -1
  56. package/dist/sqlite.cjs.map +1 -1
  57. package/dist/sqlite.js +4 -1
  58. package/dist/sqlite.js.map +1 -1
  59. package/dist/twilio.cjs.map +1 -1
  60. package/dist/twilio.js.map +1 -1
  61. package/dist/webhook.cjs.map +1 -1
  62. package/dist/webhook.js.map +1 -1
  63. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -65,6 +65,14 @@ function formatRelative(at, now) {
65
65
  if (abs < 5e3) return text;
66
66
  return diff < 0 ? `${text} ago` : `in ${text}`;
67
67
  }
68
+ var FIRST_DATE_MS = -621355968e5;
69
+ var LAST_DATE_MS = 253402300799999;
70
+ function isoTime(at) {
71
+ return at >= FIRST_DATE_MS && at <= LAST_DATE_MS ? new Date(at).toISOString() : null;
72
+ }
73
+ function beyondDates(at) {
74
+ return at > LAST_DATE_MS ? "after 9999-12-31 23:59:59 UTC" : "before 0001-01-01 00:00:00 UTC";
75
+ }
68
76
 
69
77
  // src/env.ts
70
78
  function readEnv(name) {
@@ -102,15 +110,36 @@ function parseSchedule(schedule, timezone) {
102
110
  cache.set(key, parsed);
103
111
  return parsed;
104
112
  }
113
+ var CYCLE_MS = 146097 * 864e5;
114
+ var CRONER_FIRST_MS = Date.UTC(400, 0, 1);
115
+ var CRONER_LAST_MS = Date.UTC(2800, 0, 1);
116
+ function runsAfter(cron, n, from) {
117
+ let shift = 0;
118
+ if (from < CRONER_FIRST_MS) shift = Math.ceil((CRONER_FIRST_MS - from) / CYCLE_MS) * CYCLE_MS;
119
+ else if (from >= CRONER_LAST_MS) shift = -(Math.floor((from - CRONER_LAST_MS) / CYCLE_MS) + 1) * CYCLE_MS;
120
+ const out = [];
121
+ for (const date of cron.nextRuns(n, new Date(from + shift))) {
122
+ const t = date.getTime() - shift;
123
+ if (t > LAST_DATE_MS) break;
124
+ out.push(t);
125
+ }
126
+ return out;
127
+ }
128
+ function countFrom(from) {
129
+ if (from >= LAST_DATE_MS) return null;
130
+ return from >= FIRST_DATE_MS ? from : FIRST_DATE_MS - 1;
131
+ }
105
132
  function fireAfter(parsed, from) {
106
133
  const cron = crons.get(parsed);
107
134
  if (!cron) throw new Error(`schedule "${parsed.source}" was not made by parseSchedule`);
108
- let probe = from;
135
+ const start = countFrom(from);
136
+ if (start === null) return null;
137
+ let probe = start;
109
138
  for (let attempt = 0; attempt < 4; attempt++) {
110
- const runs = cron.nextRuns(8, new Date(probe));
139
+ const runs = runsAfter(cron, 8, probe);
111
140
  if (runs.length === 0) return null;
112
- const found = runs.find((d) => d.getTime() > from);
113
- if (found) return found.getTime();
141
+ const found = runs.find((t) => t > start);
142
+ if (found !== void 0) return found;
114
143
  probe += 36e5;
115
144
  }
116
145
  return null;
@@ -119,20 +148,21 @@ function firesBetween(parsed, from, to, limit) {
119
148
  const cron = crons.get(parsed);
120
149
  if (!cron) throw new Error(`schedule "${parsed.source}" was not made by parseSchedule`);
121
150
  const out = [];
122
- let probe = from;
123
- let last = from;
151
+ const start = countFrom(from);
152
+ if (start === null) return out;
153
+ let probe = start;
154
+ let last = start;
124
155
  for (let guard = 0; guard < 1e3; guard++) {
125
- const batch = cron.nextRuns(Math.min(limit + 1 - out.length, 24), new Date(probe));
156
+ const batch = runsAfter(cron, Math.min(limit + 1 - out.length, 24), probe);
126
157
  if (batch.length === 0) return out;
127
- for (const date of batch) {
128
- const t = date.getTime();
158
+ for (const t of batch) {
129
159
  if (t <= last) continue;
130
160
  if (t > to) return out;
131
161
  out.push(t);
132
162
  last = t;
133
163
  if (out.length > limit) return null;
134
164
  }
135
- const end = batch[batch.length - 1].getTime();
165
+ const end = batch[batch.length - 1];
136
166
  probe = end > probe ? end : probe + 36e5;
137
167
  }
138
168
  return out;
@@ -168,6 +198,7 @@ function runCovers(startedAt, dueAt, followingAt = null) {
168
198
  }
169
199
  function inSpringForwardGap(parsed, startedAt, fireAt) {
170
200
  const LOOKBACK = 3 * 36e5;
201
+ if (fireAt - LOOKBACK < FIRST_DATE_MS) return false;
171
202
  const after2 = utcOffset(fireAt, parsed.timezone);
172
203
  const before = utcOffset(fireAt - LOOKBACK, parsed.timezone);
173
204
  const gap = after2 - before;
@@ -204,8 +235,10 @@ function utcOffset(at, timezone) {
204
235
  for (const part of format.formatToParts(new Date(at))) {
205
236
  if (part.type !== "literal") parts[part.type] = Number(part.value);
206
237
  }
207
- const wall = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
208
- return wall - Math.floor(at / 1e3) * 1e3;
238
+ const wall = /* @__PURE__ */ new Date(0);
239
+ wall.setUTCFullYear(parts.year, parts.month - 1, parts.day);
240
+ wall.setUTCHours(parts.hour, parts.minute, parts.second);
241
+ return wall.getTime() - Math.floor(at / 1e3) * 1e3;
209
242
  }
210
243
 
211
244
  // src/stats.ts
@@ -228,6 +261,15 @@ var DEFAULT_TIMEOUT_MS = 60 * 6e4;
228
261
  var SLOW_FLOOR_MS = 1e4;
229
262
  var BASELINE_MIN_RUNS = 5;
230
263
  var BASELINE_WINDOW = 20;
264
+ var MAX_DURATION_MS = Number.MAX_SAFE_INTEGER;
265
+ function runDuration(startedAt, finishedAt) {
266
+ const ms = finishedAt - startedAt;
267
+ return ms > 0 ? Math.min(ms, MAX_DURATION_MS) : 0;
268
+ }
269
+ function stateVersion(state) {
270
+ const version = state?.version;
271
+ return typeof version === "number" && Number.isSafeInteger(version) && version >= 0 ? version + 0 : 0;
272
+ }
231
273
  function emptyState(job) {
232
274
  return { job, open: {}, consecutiveFailures: 0, silencedUntil: null, lastAlertAt: null, pendingRecovery: [], undelivered: [] };
233
275
  }
@@ -438,7 +480,9 @@ function summary(stored, recent, state, nextExpectedAt, health) {
438
480
  // src/format.ts
439
481
  function when(at, now) {
440
482
  if (at === null || at === void 0) return "never";
441
- return `${new Date(at).toISOString().replace("T", " ").slice(0, 19)} UTC (${formatRelative(at, now)})`;
483
+ const iso = isoTime(at);
484
+ if (iso === null) return beyondDates(at);
485
+ return `${iso.replace("T", " ").slice(0, 19)} UTC (${formatRelative(at, now)})`;
442
486
  }
443
487
  function firstLines(text, n) {
444
488
  if (!text) return "";
@@ -834,9 +878,13 @@ function dayLabel(t) {
834
878
  return `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}`;
835
879
  }
836
880
  function when2(t, now) {
881
+ if (t > LAST_DATE_MS) return "after 31 Dec 9999 23:59";
882
+ if (!(t >= FIRST_DATE_MS)) return "before 1 Jan 0001 00:00";
837
883
  if (Math.floor(t / DAY) === Math.floor(now / DAY)) return clock(t);
838
884
  const d = new Date(t);
839
- return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} ${clock(t)}`;
885
+ const year = d.getUTCFullYear();
886
+ const other = year === new Date(now).getUTCFullYear() ? "" : ` ${String(year).padStart(4, "0")}`;
887
+ return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}${other} ${clock(t)}`;
840
888
  }
841
889
  function parsedSchedule(job) {
842
890
  if (!job.definition.schedule) return null;
@@ -1327,7 +1375,8 @@ function sparkline(runs) {
1327
1375
  }
1328
1376
  function stamp(at, now) {
1329
1377
  if (at === null) return `<span class="muted">never</span>`;
1330
- const iso = new Date(at).toISOString();
1378
+ const iso = isoTime(at);
1379
+ if (iso === null) return `<span class="nowrap">${beyondDates(at)}</span>`;
1331
1380
  return `<time class="nowrap" datetime="${iso}" title="${iso.replace("T", " ").slice(0, 19)} UTC">${h(formatRelative(at, now))}</time>`;
1332
1381
  }
1333
1382
  function healthFigures(jobs) {
@@ -1860,7 +1909,7 @@ function memory() {
1860
1909
  states.set(state.job, clone(state));
1861
1910
  },
1862
1911
  async compareAndSetState(state, expectedVersion) {
1863
- if ((states.get(state.job)?.version ?? 0) !== expectedVersion) return false;
1912
+ if (stateVersion(states.get(state.job)) !== expectedVersion) return false;
1864
1913
  states.set(state.job, clone(state));
1865
1914
  return true;
1866
1915
  },
@@ -2130,7 +2179,7 @@ var CronWatch = class {
2130
2179
  const current = await this.readState(job);
2131
2180
  const { state, result } = await change(current);
2132
2181
  if (sameState(state, current)) return { state: current, result };
2133
- const version = current.version ?? 0;
2182
+ const version = stateVersion(current);
2134
2183
  const next = { ...state, version: version + 1 };
2135
2184
  if (await this.writeState(next, version)) return { state: next, result };
2136
2185
  if (attempt >= STATE_ATTEMPTS) {
@@ -2190,7 +2239,7 @@ var CronWatch = class {
2190
2239
  }
2191
2240
  const finishedAt = this.now();
2192
2241
  run.finishedAt = finishedAt;
2193
- run.durationMs = Math.max(0, finishedAt - startedAt);
2242
+ run.durationMs = runDuration(startedAt, finishedAt);
2194
2243
  run.metrics = recorder.metrics();
2195
2244
  run.output = recorder.output() ?? (typeof result === "string" ? capOutput(result) : null);
2196
2245
  this.conclude(definition, run, result, error, threw, recorder.expectText() ?? (typeof result === "string" ? result : null));
@@ -2424,7 +2473,7 @@ ${id}`;
2424
2473
  ...from,
2425
2474
  status: "running",
2426
2475
  finishedAt,
2427
- durationMs: Math.max(0, finishedAt - from.startedAt),
2476
+ durationMs: runDuration(from.startedAt, finishedAt),
2428
2477
  error: null,
2429
2478
  output: joinOutput(from.output, added),
2430
2479
  metrics: { ...from.metrics, ...recorder.metrics() }
@@ -2613,7 +2662,7 @@ ${id}`;
2613
2662
  const timeout = timeoutMs(definition);
2614
2663
  run.status = "timeout";
2615
2664
  run.finishedAt = now;
2616
- run.durationMs = now - run.startedAt;
2665
+ run.durationMs = runDuration(run.startedAt, now);
2617
2666
  run.error = `Still running after ${formatDuration(timeout)}; marked as timed out`;
2618
2667
  if (!await this.writeRunIf(run, ["running"])) continue;
2619
2668
  alerts.push(...await this.finishRun(definition, run, now));