@cronwatch/sdk 0.14.0 → 0.14.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.
Files changed (67) hide show
  1. package/dist/bugsnag.cjs +19 -4
  2. package/dist/bugsnag.cjs.map +1 -1
  3. package/dist/bugsnag.js +19 -4
  4. package/dist/bugsnag.js.map +1 -1
  5. package/dist/datadog.cjs +19 -4
  6. package/dist/datadog.cjs.map +1 -1
  7. package/dist/datadog.js +19 -4
  8. package/dist/datadog.js.map +1 -1
  9. package/dist/discord.cjs +27 -9
  10. package/dist/discord.cjs.map +1 -1
  11. package/dist/discord.js +27 -9
  12. package/dist/discord.js.map +1 -1
  13. package/dist/honeybadger.cjs +19 -4
  14. package/dist/honeybadger.cjs.map +1 -1
  15. package/dist/honeybadger.js +19 -4
  16. package/dist/honeybadger.js.map +1 -1
  17. package/dist/index.cjs +33 -17
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +33 -17
  22. package/dist/index.js.map +1 -1
  23. package/dist/mailgun.cjs +19 -4
  24. package/dist/mailgun.cjs.map +1 -1
  25. package/dist/mailgun.js +19 -4
  26. package/dist/mailgun.js.map +1 -1
  27. package/dist/newrelic.cjs +19 -4
  28. package/dist/newrelic.cjs.map +1 -1
  29. package/dist/newrelic.js +19 -4
  30. package/dist/newrelic.js.map +1 -1
  31. package/dist/postmark.cjs +19 -4
  32. package/dist/postmark.cjs.map +1 -1
  33. package/dist/postmark.js +19 -4
  34. package/dist/postmark.js.map +1 -1
  35. package/dist/resend.cjs +19 -4
  36. package/dist/resend.cjs.map +1 -1
  37. package/dist/resend.js +19 -4
  38. package/dist/resend.js.map +1 -1
  39. package/dist/rollbar.cjs +19 -4
  40. package/dist/rollbar.cjs.map +1 -1
  41. package/dist/rollbar.js +19 -4
  42. package/dist/rollbar.js.map +1 -1
  43. package/dist/sendgrid.cjs +19 -4
  44. package/dist/sendgrid.cjs.map +1 -1
  45. package/dist/sendgrid.js +19 -4
  46. package/dist/sendgrid.js.map +1 -1
  47. package/dist/sentry.cjs +19 -4
  48. package/dist/sentry.cjs.map +1 -1
  49. package/dist/sentry.js +19 -4
  50. package/dist/sentry.js.map +1 -1
  51. package/dist/ses.cjs +19 -4
  52. package/dist/ses.cjs.map +1 -1
  53. package/dist/ses.js +19 -4
  54. package/dist/ses.js.map +1 -1
  55. package/dist/slack.cjs +27 -9
  56. package/dist/slack.cjs.map +1 -1
  57. package/dist/slack.js +27 -9
  58. package/dist/slack.js.map +1 -1
  59. package/dist/twilio.cjs +19 -4
  60. package/dist/twilio.cjs.map +1 -1
  61. package/dist/twilio.js +19 -4
  62. package/dist/twilio.js.map +1 -1
  63. package/dist/webhook.cjs +29 -7
  64. package/dist/webhook.cjs.map +1 -1
  65. package/dist/webhook.js +29 -7
  66. package/dist/webhook.js.map +1 -1
  67. package/package.json +1 -1
package/dist/index.d.cts CHANGED
@@ -11,7 +11,7 @@ export { a as AlertChannel, d as AlertDetails, e as AlertType, B as BudgetBreach
11
11
  declare function memory(): Store & Required<Pick<Store, "lastRun">>;
12
12
 
13
13
  /** This release of @cronwatch/sdk. scripts/release.mjs keeps it in step with package.json. */
14
- declare const VERSION = "0.14.0";
14
+ declare const VERSION = "0.14.1";
15
15
 
16
16
  /**
17
17
  * "15m" -> 900000. Accepts a plain number of milliseconds, and compound
package/dist/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export { a as AlertChannel, d as AlertDetails, e as AlertType, B as BudgetBreach
11
11
  declare function memory(): Store & Required<Pick<Store, "lastRun">>;
12
12
 
13
13
  /** This release of @cronwatch/sdk. scripts/release.mjs keeps it in step with package.json. */
14
- declare const VERSION = "0.14.0";
14
+ declare const VERSION = "0.14.1";
15
15
 
16
16
  /**
17
17
  * "15m" -> 900000. Accepts a plain number of milliseconds, and compound
package/dist/index.js CHANGED
@@ -991,11 +991,15 @@ function stringify(part) {
991
991
  }
992
992
  }
993
993
  function createRecorder(run) {
994
+ const window = OUTPUT_CAP + REDACT_EDGE + 2;
995
+ const copy = (text) => JSON.parse(JSON.stringify(text));
994
996
  const lines = [];
995
997
  let size = 0;
996
998
  const head = [];
997
999
  let headSize = 0;
998
- let dropped = false;
1000
+ let whole = "";
1001
+ let tail2 = "";
1002
+ let logged = false;
999
1003
  const metrics = {};
1000
1004
  const controller = new AbortController();
1001
1005
  const context = {
@@ -1004,16 +1008,23 @@ function createRecorder(run) {
1004
1008
  startedAt: run.startedAt,
1005
1009
  signal: controller.signal,
1006
1010
  log(...parts) {
1007
- const line = parts.map(stringify).join(" ");
1011
+ let line = parts.map(stringify).join(" ");
1012
+ const separator = logged ? "\n" : "";
1013
+ logged = true;
1014
+ if (whole !== null) whole = whole.length + separator.length + line.length <= 2 * OUTPUT_CAP ? whole + separator + line : null;
1015
+ tail2 = copy((tail2 + separator + copy(line.slice(-OUTPUT_CAP))).slice(-OUTPUT_CAP));
1008
1016
  if (headSize < OUTPUT_CAP) {
1009
- head.push(line);
1017
+ head.push(copy(line.slice(0, OUTPUT_CAP - headSize)));
1010
1018
  headSize += line.length + 1;
1011
1019
  }
1020
+ line = stripNul(line);
1021
+ if (line.length >= window) {
1022
+ line = copy(line.slice(-68609));
1023
+ }
1012
1024
  lines.push(line);
1013
1025
  size += line.length + 1;
1014
- while (size > 64 * 1024 && lines.length > 1) {
1026
+ while (size > window && lines.length > 1) {
1015
1027
  size -= lines.shift().length + 1;
1016
- dropped = true;
1017
1028
  }
1018
1029
  },
1019
1030
  metric(name, value) {
@@ -1030,10 +1041,8 @@ function createRecorder(run) {
1030
1041
  context,
1031
1042
  output: () => lines.length === 0 ? null : lines.join("\n"),
1032
1043
  expectText: () => {
1033
- if (lines.length === 0) return null;
1034
- const all = lines.join("\n");
1035
- if (!dropped && all.length <= 2 * OUTPUT_CAP) return all;
1036
- return head.join("\n").slice(0, OUTPUT_CAP) + "\n" + all.slice(-OUTPUT_CAP);
1044
+ if (!logged) return null;
1045
+ return whole ?? head.join("\n").slice(0, OUTPUT_CAP) + "\n" + tail2;
1037
1046
  },
1038
1047
  metrics: () => ({ ...metrics }),
1039
1048
  abort: () => controller.abort()
@@ -1207,7 +1216,7 @@ function staticAsset(path, base) {
1207
1216
  }
1208
1217
 
1209
1218
  // src/version.ts
1210
- var VERSION = "0.14.0";
1219
+ var VERSION = "0.14.1";
1211
1220
  var API_VERSION = 1;
1212
1221
 
1213
1222
  // src/routes/timeline.ts
@@ -1997,6 +2006,7 @@ function stripBase(pathname, base) {
1997
2006
  }
1998
2007
  var MAX_REQUEST_BODY = 1 << 20;
1999
2008
  var TOO_LARGE = /* @__PURE__ */ Symbol("too large");
2009
+ var BAD_BODY = /* @__PURE__ */ Symbol("malformed body");
2000
2010
  async function readLimited(request) {
2001
2011
  const length = Number(request.headers.get("content-length"));
2002
2012
  if (Number.isFinite(length) && length > MAX_REQUEST_BODY) return TOO_LARGE;
@@ -2043,14 +2053,14 @@ async function readBody(request) {
2043
2053
  try {
2044
2054
  if (json2) {
2045
2055
  const data = await copy.json();
2046
- return typeof data === "object" && data !== null ? Object.fromEntries(Object.entries(data).map(([k, v]) => [k, String(v)])) : {};
2056
+ return typeof data === "object" && data !== null && !Array.isArray(data) ? Object.fromEntries(Object.entries(data).map(([k, v]) => [k, String(v)])) : BAD_BODY;
2047
2057
  }
2048
2058
  const fields = await copy.formData();
2049
2059
  const out = {};
2050
2060
  for (const [k, v] of fields.entries()) out[k] = String(v);
2051
2061
  return out;
2052
2062
  } catch {
2053
- return {};
2063
+ return BAD_BODY;
2054
2064
  }
2055
2065
  }
2056
2066
  function crossSite(request, publicOrigin) {
@@ -2116,6 +2126,7 @@ function buildRoutes(cw, options = {}) {
2116
2126
  const signInPage = () => generated ? html(messagePage("Sign in", "CRONWATCH_TOKEN is not set, so this development server made a token. The sign-in link is in the server log: open it once, or enter the token from it below, and this browser stays signed in.", base, true), 401) : html(messagePage("Sign in", "Enter your CRONWATCH_TOKEN and this browser stays signed in.", base, true), 401);
2117
2127
  if (token && method === "POST" && path === "/signin") {
2118
2128
  const body = await readBody(request);
2129
+ if (body === BAD_BODY) return html(messagePage("Bad request", "Malformed request body", base), 400);
2119
2130
  const sent = body === TOO_LARGE ? void 0 : body.token;
2120
2131
  if (sent === void 0 || !constantTimeEqual(sent, token)) return signInPage();
2121
2132
  return redirect(signInReturn(request.headers.get("referer"), publicOrigin, base), { "set-cookie": await signInCookie() });
@@ -2173,6 +2184,7 @@ function buildRoutes(cw, options = {}) {
2173
2184
  if (parts[2] === "silence") {
2174
2185
  const body = await readBody(request);
2175
2186
  if (body === TOO_LARGE) return html(messagePage("Not silenced", "The request was too large.", base), 413);
2187
+ if (body === BAD_BODY) return html(messagePage("Not silenced", "Malformed request body", base), 400);
2176
2188
  let duration;
2177
2189
  try {
2178
2190
  duration = silenceDuration(body.for);
@@ -2210,6 +2222,7 @@ function buildRoutes(cw, options = {}) {
2210
2222
  if (rest[2] === "silence") {
2211
2223
  const body = await readBody(request);
2212
2224
  if (body === TOO_LARGE) return api({ ok: false, error: "Request body too large" }, 413);
2225
+ if (body === BAD_BODY) return api({ ok: false, error: "Malformed request body" }, 400);
2213
2226
  let duration;
2214
2227
  try {
2215
2228
  duration = silenceDuration(body.for ?? url.searchParams.get("for"));
@@ -2845,9 +2858,10 @@ var Cronwatch = class {
2845
2858
  if (stored.job !== run.job) return `belongs to job "${stored.job}"`;
2846
2859
  }
2847
2860
  }
2861
+ const tracked = (await this.readState(run.job)).pendingRuns?.includes(run.id) ?? false;
2848
2862
  const claim = await this.claimFinish(run);
2849
2863
  if ("ignored" in claim) return claim.ignored;
2850
- if (!claim.lateAfterTimeout || run.status === "ok") await this.finishRun(toStored(definition), run, finishedAt);
2864
+ if (!claim.lateAfterTimeout || run.status === "ok") await this.finishRun(toStored(definition), run, finishedAt, tracked && !claim.lateAfterTimeout);
2851
2865
  return null;
2852
2866
  }
2853
2867
  /** A conditional write (Store.updateRunIf), or for a store without one, a read then a plain write. */
@@ -3142,7 +3156,7 @@ ${id}`;
3142
3156
  if (!evaluate) return [];
3143
3157
  await this.updateState(run.job, (before) => ({ state: holdRun(onRunStart(definition, run, before, this.now()), run.id), result: void 0 }));
3144
3158
  if (run.status === "running") return [];
3145
- return this.finishRun(definition, run, this.now());
3159
+ return this.finishRun(definition, run, this.now(), true);
3146
3160
  }
3147
3161
  /** recordRun() for a run already stored. */
3148
3162
  async recordOver(definition, stored, run, evaluate) {
@@ -3151,6 +3165,7 @@ ${id}`;
3151
3165
  return [];
3152
3166
  }
3153
3167
  if (stored.status !== "running" && stored.status !== "timeout" || run.status === "running") return [];
3168
+ const tracked = (await this.readState(run.job)).pendingRuns?.includes(run.id) ?? false;
3154
3169
  const claim = await this.claimFinish(run);
3155
3170
  if ("ignored" in claim) {
3156
3171
  this.report(new Error(`run ${run.id} of ${run.job} ${claim.ignored}; ignored`), `recording ${run.job}`);
@@ -3161,18 +3176,19 @@ ${id}`;
3161
3176
  return [];
3162
3177
  }
3163
3178
  if (claim.lateAfterTimeout && run.status !== "ok") return [];
3164
- return this.finishRun(definition, run, this.now());
3179
+ return this.finishRun(definition, run, this.now(), tracked && !claim.lateAfterTimeout);
3165
3180
  }
3166
3181
  /**
3167
3182
  * Evaluate a finished run (ok, failed, or timed out by a check), already
3168
3183
  * written, against the job's state and send what that produces. The
3169
3184
  * alerts are written with that state (see outbox()). Never throws.
3170
3185
  */
3171
- async finishRun(definition, run, now) {
3186
+ async finishRun(definition, run, now, pendingOnly = false) {
3172
3187
  let held;
3173
3188
  try {
3174
3189
  let history = null;
3175
3190
  ({ result: held } = await this.updateState(run.job, async (previous) => {
3191
+ if (pendingOnly && !(previous.pendingRuns ?? []).includes(run.id)) return { state: previous, result: { alerts: [], dropped: 0 } };
3176
3192
  history ??= await this.history(run);
3177
3193
  const before = releaseRun(previous, run.id);
3178
3194
  return this.outbox(applySilence(before, onRunFinish(definition, run, before, history, now), now), definition, now);
@@ -3317,7 +3333,7 @@ ${id}`;
3317
3333
  continue;
3318
3334
  }
3319
3335
  if (run.status === "running" || typeof run.finishedAt !== "number" || run.finishedAt > now - SEND_LEASE_MS) continue;
3320
- alerts.push(...await this.finishRun(stored.definition, run, now));
3336
+ alerts.push(...await this.finishRun(stored.definition, run, now, true));
3321
3337
  } catch (e) {
3322
3338
  this.onError(e, `evaluating run ${id} of ${stored.name}`);
3323
3339
  }