@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.cjs CHANGED
@@ -67,6 +67,14 @@ function formatRelative(at, now) {
67
67
  if (abs < 5e3) return text;
68
68
  return diff < 0 ? `${text} ago` : `in ${text}`;
69
69
  }
70
+ var FIRST_DATE_MS = -621355968e5;
71
+ var LAST_DATE_MS = 253402300799999;
72
+ function isoTime(at) {
73
+ return at >= FIRST_DATE_MS && at <= LAST_DATE_MS ? new Date(at).toISOString() : null;
74
+ }
75
+ function beyondDates(at) {
76
+ return at > LAST_DATE_MS ? "after 9999-12-31 23:59:59 UTC" : "before 0001-01-01 00:00:00 UTC";
77
+ }
70
78
 
71
79
  // src/env.ts
72
80
  function readEnv(name) {
@@ -104,15 +112,36 @@ function parseSchedule(schedule, timezone) {
104
112
  cache.set(key, parsed);
105
113
  return parsed;
106
114
  }
115
+ var CYCLE_MS = 146097 * 864e5;
116
+ var CRONER_FIRST_MS = Date.UTC(400, 0, 1);
117
+ var CRONER_LAST_MS = Date.UTC(2800, 0, 1);
118
+ function runsAfter(cron, n, from) {
119
+ let shift = 0;
120
+ if (from < CRONER_FIRST_MS) shift = Math.ceil((CRONER_FIRST_MS - from) / CYCLE_MS) * CYCLE_MS;
121
+ else if (from >= CRONER_LAST_MS) shift = -(Math.floor((from - CRONER_LAST_MS) / CYCLE_MS) + 1) * CYCLE_MS;
122
+ const out = [];
123
+ for (const date of cron.nextRuns(n, new Date(from + shift))) {
124
+ const t = date.getTime() - shift;
125
+ if (t > LAST_DATE_MS) break;
126
+ out.push(t);
127
+ }
128
+ return out;
129
+ }
130
+ function countFrom(from) {
131
+ if (from >= LAST_DATE_MS) return null;
132
+ return from >= FIRST_DATE_MS ? from : FIRST_DATE_MS - 1;
133
+ }
107
134
  function fireAfter(parsed, from) {
108
135
  const cron = crons.get(parsed);
109
136
  if (!cron) throw new Error(`schedule "${parsed.source}" was not made by parseSchedule`);
110
- let probe = from;
137
+ const start = countFrom(from);
138
+ if (start === null) return null;
139
+ let probe = start;
111
140
  for (let attempt = 0; attempt < 4; attempt++) {
112
- const runs = cron.nextRuns(8, new Date(probe));
141
+ const runs = runsAfter(cron, 8, probe);
113
142
  if (runs.length === 0) return null;
114
- const found = runs.find((d) => d.getTime() > from);
115
- if (found) return found.getTime();
143
+ const found = runs.find((t) => t > start);
144
+ if (found !== void 0) return found;
116
145
  probe += 36e5;
117
146
  }
118
147
  return null;
@@ -121,20 +150,21 @@ function firesBetween(parsed, from, to, limit) {
121
150
  const cron = crons.get(parsed);
122
151
  if (!cron) throw new Error(`schedule "${parsed.source}" was not made by parseSchedule`);
123
152
  const out = [];
124
- let probe = from;
125
- let last = from;
153
+ const start = countFrom(from);
154
+ if (start === null) return out;
155
+ let probe = start;
156
+ let last = start;
126
157
  for (let guard = 0; guard < 1e3; guard++) {
127
- const batch = cron.nextRuns(Math.min(limit + 1 - out.length, 24), new Date(probe));
158
+ const batch = runsAfter(cron, Math.min(limit + 1 - out.length, 24), probe);
128
159
  if (batch.length === 0) return out;
129
- for (const date of batch) {
130
- const t = date.getTime();
160
+ for (const t of batch) {
131
161
  if (t <= last) continue;
132
162
  if (t > to) return out;
133
163
  out.push(t);
134
164
  last = t;
135
165
  if (out.length > limit) return null;
136
166
  }
137
- const end = batch[batch.length - 1].getTime();
167
+ const end = batch[batch.length - 1];
138
168
  probe = end > probe ? end : probe + 36e5;
139
169
  }
140
170
  return out;
@@ -170,6 +200,7 @@ function runCovers(startedAt, dueAt, followingAt = null) {
170
200
  }
171
201
  function inSpringForwardGap(parsed, startedAt, fireAt) {
172
202
  const LOOKBACK = 3 * 36e5;
203
+ if (fireAt - LOOKBACK < FIRST_DATE_MS) return false;
173
204
  const after2 = utcOffset(fireAt, parsed.timezone);
174
205
  const before = utcOffset(fireAt - LOOKBACK, parsed.timezone);
175
206
  const gap = after2 - before;
@@ -206,8 +237,10 @@ function utcOffset(at, timezone) {
206
237
  for (const part of format.formatToParts(new Date(at))) {
207
238
  if (part.type !== "literal") parts[part.type] = Number(part.value);
208
239
  }
209
- const wall = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
210
- return wall - Math.floor(at / 1e3) * 1e3;
240
+ const wall = /* @__PURE__ */ new Date(0);
241
+ wall.setUTCFullYear(parts.year, parts.month - 1, parts.day);
242
+ wall.setUTCHours(parts.hour, parts.minute, parts.second);
243
+ return wall.getTime() - Math.floor(at / 1e3) * 1e3;
211
244
  }
212
245
 
213
246
  // src/stats.ts
@@ -230,6 +263,15 @@ var DEFAULT_TIMEOUT_MS = 60 * 6e4;
230
263
  var SLOW_FLOOR_MS = 1e4;
231
264
  var BASELINE_MIN_RUNS = 5;
232
265
  var BASELINE_WINDOW = 20;
266
+ var MAX_DURATION_MS = Number.MAX_SAFE_INTEGER;
267
+ function runDuration(startedAt, finishedAt) {
268
+ const ms = finishedAt - startedAt;
269
+ return ms > 0 ? Math.min(ms, MAX_DURATION_MS) : 0;
270
+ }
271
+ function stateVersion(state) {
272
+ const version = state?.version;
273
+ return typeof version === "number" && Number.isSafeInteger(version) && version >= 0 ? version + 0 : 0;
274
+ }
233
275
  function emptyState(job) {
234
276
  return { job, open: {}, consecutiveFailures: 0, silencedUntil: null, lastAlertAt: null, pendingRecovery: [], undelivered: [] };
235
277
  }
@@ -440,7 +482,9 @@ function summary(stored, recent, state, nextExpectedAt, health) {
440
482
  // src/format.ts
441
483
  function when(at, now) {
442
484
  if (at === null || at === void 0) return "never";
443
- return `${new Date(at).toISOString().replace("T", " ").slice(0, 19)} UTC (${formatRelative(at, now)})`;
485
+ const iso = isoTime(at);
486
+ if (iso === null) return beyondDates(at);
487
+ return `${iso.replace("T", " ").slice(0, 19)} UTC (${formatRelative(at, now)})`;
444
488
  }
445
489
  function firstLines(text, n) {
446
490
  if (!text) return "";
@@ -836,9 +880,13 @@ function dayLabel(t) {
836
880
  return `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}`;
837
881
  }
838
882
  function when2(t, now) {
883
+ if (t > LAST_DATE_MS) return "after 31 Dec 9999 23:59";
884
+ if (!(t >= FIRST_DATE_MS)) return "before 1 Jan 0001 00:00";
839
885
  if (Math.floor(t / DAY) === Math.floor(now / DAY)) return clock(t);
840
886
  const d = new Date(t);
841
- return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} ${clock(t)}`;
887
+ const year = d.getUTCFullYear();
888
+ const other = year === new Date(now).getUTCFullYear() ? "" : ` ${String(year).padStart(4, "0")}`;
889
+ return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]}${other} ${clock(t)}`;
842
890
  }
843
891
  function parsedSchedule(job) {
844
892
  if (!job.definition.schedule) return null;
@@ -1329,7 +1377,8 @@ function sparkline(runs) {
1329
1377
  }
1330
1378
  function stamp(at, now) {
1331
1379
  if (at === null) return `<span class="muted">never</span>`;
1332
- const iso = new Date(at).toISOString();
1380
+ const iso = isoTime(at);
1381
+ if (iso === null) return `<span class="nowrap">${beyondDates(at)}</span>`;
1333
1382
  return `<time class="nowrap" datetime="${iso}" title="${iso.replace("T", " ").slice(0, 19)} UTC">${h(formatRelative(at, now))}</time>`;
1334
1383
  }
1335
1384
  function healthFigures(jobs) {
@@ -1862,7 +1911,7 @@ function memory() {
1862
1911
  states.set(state.job, clone(state));
1863
1912
  },
1864
1913
  async compareAndSetState(state, expectedVersion) {
1865
- if ((states.get(state.job)?.version ?? 0) !== expectedVersion) return false;
1914
+ if (stateVersion(states.get(state.job)) !== expectedVersion) return false;
1866
1915
  states.set(state.job, clone(state));
1867
1916
  return true;
1868
1917
  },
@@ -2132,7 +2181,7 @@ var CronWatch = class {
2132
2181
  const current = await this.readState(job);
2133
2182
  const { state, result } = await change(current);
2134
2183
  if (sameState(state, current)) return { state: current, result };
2135
- const version = current.version ?? 0;
2184
+ const version = stateVersion(current);
2136
2185
  const next = { ...state, version: version + 1 };
2137
2186
  if (await this.writeState(next, version)) return { state: next, result };
2138
2187
  if (attempt >= STATE_ATTEMPTS) {
@@ -2192,7 +2241,7 @@ var CronWatch = class {
2192
2241
  }
2193
2242
  const finishedAt = this.now();
2194
2243
  run.finishedAt = finishedAt;
2195
- run.durationMs = Math.max(0, finishedAt - startedAt);
2244
+ run.durationMs = runDuration(startedAt, finishedAt);
2196
2245
  run.metrics = recorder.metrics();
2197
2246
  run.output = recorder.output() ?? (typeof result === "string" ? capOutput(result) : null);
2198
2247
  this.conclude(definition, run, result, error, threw, recorder.expectText() ?? (typeof result === "string" ? result : null));
@@ -2426,7 +2475,7 @@ ${id}`;
2426
2475
  ...from,
2427
2476
  status: "running",
2428
2477
  finishedAt,
2429
- durationMs: Math.max(0, finishedAt - from.startedAt),
2478
+ durationMs: runDuration(from.startedAt, finishedAt),
2430
2479
  error: null,
2431
2480
  output: joinOutput(from.output, added),
2432
2481
  metrics: { ...from.metrics, ...recorder.metrics() }
@@ -2615,7 +2664,7 @@ ${id}`;
2615
2664
  const timeout = timeoutMs(definition);
2616
2665
  run.status = "timeout";
2617
2666
  run.finishedAt = now;
2618
- run.durationMs = now - run.startedAt;
2667
+ run.durationMs = runDuration(run.startedAt, now);
2619
2668
  run.error = `Still running after ${formatDuration(timeout)}; marked as timed out`;
2620
2669
  if (!await this.writeRunIf(run, ["running"])) continue;
2621
2670
  alerts.push(...await this.finishRun(definition, run, now));