@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.cjs CHANGED
@@ -993,11 +993,15 @@ function stringify(part) {
993
993
  }
994
994
  }
995
995
  function createRecorder(run) {
996
+ const window = OUTPUT_CAP + REDACT_EDGE + 2;
997
+ const copy = (text) => JSON.parse(JSON.stringify(text));
996
998
  const lines = [];
997
999
  let size = 0;
998
1000
  const head = [];
999
1001
  let headSize = 0;
1000
- let dropped = false;
1002
+ let whole = "";
1003
+ let tail2 = "";
1004
+ let logged = false;
1001
1005
  const metrics = {};
1002
1006
  const controller = new AbortController();
1003
1007
  const context = {
@@ -1006,16 +1010,23 @@ function createRecorder(run) {
1006
1010
  startedAt: run.startedAt,
1007
1011
  signal: controller.signal,
1008
1012
  log(...parts) {
1009
- const line = parts.map(stringify).join(" ");
1013
+ let line = parts.map(stringify).join(" ");
1014
+ const separator = logged ? "\n" : "";
1015
+ logged = true;
1016
+ if (whole !== null) whole = whole.length + separator.length + line.length <= 2 * OUTPUT_CAP ? whole + separator + line : null;
1017
+ tail2 = copy((tail2 + separator + copy(line.slice(-OUTPUT_CAP))).slice(-OUTPUT_CAP));
1010
1018
  if (headSize < OUTPUT_CAP) {
1011
- head.push(line);
1019
+ head.push(copy(line.slice(0, OUTPUT_CAP - headSize)));
1012
1020
  headSize += line.length + 1;
1013
1021
  }
1022
+ line = stripNul(line);
1023
+ if (line.length >= window) {
1024
+ line = copy(line.slice(-68609));
1025
+ }
1014
1026
  lines.push(line);
1015
1027
  size += line.length + 1;
1016
- while (size > 64 * 1024 && lines.length > 1) {
1028
+ while (size > window && lines.length > 1) {
1017
1029
  size -= lines.shift().length + 1;
1018
- dropped = true;
1019
1030
  }
1020
1031
  },
1021
1032
  metric(name, value) {
@@ -1032,10 +1043,8 @@ function createRecorder(run) {
1032
1043
  context,
1033
1044
  output: () => lines.length === 0 ? null : lines.join("\n"),
1034
1045
  expectText: () => {
1035
- if (lines.length === 0) return null;
1036
- const all = lines.join("\n");
1037
- if (!dropped && all.length <= 2 * OUTPUT_CAP) return all;
1038
- return head.join("\n").slice(0, OUTPUT_CAP) + "\n" + all.slice(-OUTPUT_CAP);
1046
+ if (!logged) return null;
1047
+ return whole ?? head.join("\n").slice(0, OUTPUT_CAP) + "\n" + tail2;
1039
1048
  },
1040
1049
  metrics: () => ({ ...metrics }),
1041
1050
  abort: () => controller.abort()
@@ -1209,7 +1218,7 @@ function staticAsset(path, base) {
1209
1218
  }
1210
1219
 
1211
1220
  // src/version.ts
1212
- var VERSION = "0.14.0";
1221
+ var VERSION = "0.14.1";
1213
1222
  var API_VERSION = 1;
1214
1223
 
1215
1224
  // src/routes/timeline.ts
@@ -1999,6 +2008,7 @@ function stripBase(pathname, base) {
1999
2008
  }
2000
2009
  var MAX_REQUEST_BODY = 1 << 20;
2001
2010
  var TOO_LARGE = /* @__PURE__ */ Symbol("too large");
2011
+ var BAD_BODY = /* @__PURE__ */ Symbol("malformed body");
2002
2012
  async function readLimited(request) {
2003
2013
  const length = Number(request.headers.get("content-length"));
2004
2014
  if (Number.isFinite(length) && length > MAX_REQUEST_BODY) return TOO_LARGE;
@@ -2045,14 +2055,14 @@ async function readBody(request) {
2045
2055
  try {
2046
2056
  if (json2) {
2047
2057
  const data = await copy.json();
2048
- return typeof data === "object" && data !== null ? Object.fromEntries(Object.entries(data).map(([k, v]) => [k, String(v)])) : {};
2058
+ return typeof data === "object" && data !== null && !Array.isArray(data) ? Object.fromEntries(Object.entries(data).map(([k, v]) => [k, String(v)])) : BAD_BODY;
2049
2059
  }
2050
2060
  const fields = await copy.formData();
2051
2061
  const out = {};
2052
2062
  for (const [k, v] of fields.entries()) out[k] = String(v);
2053
2063
  return out;
2054
2064
  } catch {
2055
- return {};
2065
+ return BAD_BODY;
2056
2066
  }
2057
2067
  }
2058
2068
  function crossSite(request, publicOrigin) {
@@ -2118,6 +2128,7 @@ function buildRoutes(cw, options = {}) {
2118
2128
  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);
2119
2129
  if (token && method === "POST" && path === "/signin") {
2120
2130
  const body = await readBody(request);
2131
+ if (body === BAD_BODY) return html(messagePage("Bad request", "Malformed request body", base), 400);
2121
2132
  const sent = body === TOO_LARGE ? void 0 : body.token;
2122
2133
  if (sent === void 0 || !constantTimeEqual(sent, token)) return signInPage();
2123
2134
  return redirect(signInReturn(request.headers.get("referer"), publicOrigin, base), { "set-cookie": await signInCookie() });
@@ -2175,6 +2186,7 @@ function buildRoutes(cw, options = {}) {
2175
2186
  if (parts[2] === "silence") {
2176
2187
  const body = await readBody(request);
2177
2188
  if (body === TOO_LARGE) return html(messagePage("Not silenced", "The request was too large.", base), 413);
2189
+ if (body === BAD_BODY) return html(messagePage("Not silenced", "Malformed request body", base), 400);
2178
2190
  let duration;
2179
2191
  try {
2180
2192
  duration = silenceDuration(body.for);
@@ -2212,6 +2224,7 @@ function buildRoutes(cw, options = {}) {
2212
2224
  if (rest[2] === "silence") {
2213
2225
  const body = await readBody(request);
2214
2226
  if (body === TOO_LARGE) return api({ ok: false, error: "Request body too large" }, 413);
2227
+ if (body === BAD_BODY) return api({ ok: false, error: "Malformed request body" }, 400);
2215
2228
  let duration;
2216
2229
  try {
2217
2230
  duration = silenceDuration(body.for ?? url.searchParams.get("for"));
@@ -2847,9 +2860,10 @@ var Cronwatch = class {
2847
2860
  if (stored.job !== run.job) return `belongs to job "${stored.job}"`;
2848
2861
  }
2849
2862
  }
2863
+ const tracked = (await this.readState(run.job)).pendingRuns?.includes(run.id) ?? false;
2850
2864
  const claim = await this.claimFinish(run);
2851
2865
  if ("ignored" in claim) return claim.ignored;
2852
- if (!claim.lateAfterTimeout || run.status === "ok") await this.finishRun(toStored(definition), run, finishedAt);
2866
+ if (!claim.lateAfterTimeout || run.status === "ok") await this.finishRun(toStored(definition), run, finishedAt, tracked && !claim.lateAfterTimeout);
2853
2867
  return null;
2854
2868
  }
2855
2869
  /** A conditional write (Store.updateRunIf), or for a store without one, a read then a plain write. */
@@ -3144,7 +3158,7 @@ ${id}`;
3144
3158
  if (!evaluate) return [];
3145
3159
  await this.updateState(run.job, (before) => ({ state: holdRun(onRunStart(definition, run, before, this.now()), run.id), result: void 0 }));
3146
3160
  if (run.status === "running") return [];
3147
- return this.finishRun(definition, run, this.now());
3161
+ return this.finishRun(definition, run, this.now(), true);
3148
3162
  }
3149
3163
  /** recordRun() for a run already stored. */
3150
3164
  async recordOver(definition, stored, run, evaluate) {
@@ -3153,6 +3167,7 @@ ${id}`;
3153
3167
  return [];
3154
3168
  }
3155
3169
  if (stored.status !== "running" && stored.status !== "timeout" || run.status === "running") return [];
3170
+ const tracked = (await this.readState(run.job)).pendingRuns?.includes(run.id) ?? false;
3156
3171
  const claim = await this.claimFinish(run);
3157
3172
  if ("ignored" in claim) {
3158
3173
  this.report(new Error(`run ${run.id} of ${run.job} ${claim.ignored}; ignored`), `recording ${run.job}`);
@@ -3163,18 +3178,19 @@ ${id}`;
3163
3178
  return [];
3164
3179
  }
3165
3180
  if (claim.lateAfterTimeout && run.status !== "ok") return [];
3166
- return this.finishRun(definition, run, this.now());
3181
+ return this.finishRun(definition, run, this.now(), tracked && !claim.lateAfterTimeout);
3167
3182
  }
3168
3183
  /**
3169
3184
  * Evaluate a finished run (ok, failed, or timed out by a check), already
3170
3185
  * written, against the job's state and send what that produces. The
3171
3186
  * alerts are written with that state (see outbox()). Never throws.
3172
3187
  */
3173
- async finishRun(definition, run, now) {
3188
+ async finishRun(definition, run, now, pendingOnly = false) {
3174
3189
  let held;
3175
3190
  try {
3176
3191
  let history = null;
3177
3192
  ({ result: held } = await this.updateState(run.job, async (previous) => {
3193
+ if (pendingOnly && !(previous.pendingRuns ?? []).includes(run.id)) return { state: previous, result: { alerts: [], dropped: 0 } };
3178
3194
  history ??= await this.history(run);
3179
3195
  const before = releaseRun(previous, run.id);
3180
3196
  return this.outbox(applySilence(before, onRunFinish(definition, run, before, history, now), now), definition, now);
@@ -3319,7 +3335,7 @@ ${id}`;
3319
3335
  continue;
3320
3336
  }
3321
3337
  if (run.status === "running" || typeof run.finishedAt !== "number" || run.finishedAt > now - SEND_LEASE_MS) continue;
3322
- alerts.push(...await this.finishRun(stored.definition, run, now));
3338
+ alerts.push(...await this.finishRun(stored.definition, run, now, true));
3323
3339
  } catch (e) {
3324
3340
  this.onError(e, `evaluating run ${id} of ${stored.name}`);
3325
3341
  }