@spfn/notification 0.1.0-beta.2 → 0.1.0-beta.3

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/server.d.ts CHANGED
@@ -736,6 +736,12 @@ interface ErrorSlackOptions {
736
736
  * @default 500
737
737
  */
738
738
  minStatusCode?: number;
739
+ /**
740
+ * Throttle window in milliseconds.
741
+ * Duplicate errors (same name + statusCode + path) within this window are suppressed.
742
+ * @default 60_000
743
+ */
744
+ throttleMs?: number;
739
745
  /**
740
746
  * Webhook URL override (defaults to env/config)
741
747
  */
@@ -752,6 +758,7 @@ interface ErrorSlackOptions {
752
758
  * Create an onError callback that sends Slack notifications
753
759
  *
754
760
  * Returns a function matching ErrorHandler's onError signature.
761
+ * Duplicate errors (same name + statusCode + path) within `throttleMs` are suppressed.
755
762
  */
756
763
  declare function createErrorSlackNotifier(options?: ErrorSlackOptions): (err: Error, ctx: ErrorContext) => Promise<void>;
757
764
 
package/dist/server.js CHANGED
@@ -3900,7 +3900,11 @@ function shortStack(err, maxLines = 3) {
3900
3900
  const lines = err.stack.split("\n").slice(1);
3901
3901
  return lines.slice(0, maxLines).map((line) => line.trim()).join("\n");
3902
3902
  }
3903
- function defaultFormat(err, ctx) {
3903
+ var throttleMap = /* @__PURE__ */ new Map();
3904
+ function throttleKey(err, ctx) {
3905
+ return `${err.name}:${ctx.statusCode}:${ctx.path}`;
3906
+ }
3907
+ function defaultFormat(err, ctx, suppressed = 0) {
3904
3908
  const emoji = ctx.statusCode >= 500 ? ":rotating_light:" : ":warning:";
3905
3909
  const title = `${emoji} *${err.name || "Error"}* \u2014 ${ctx.statusCode}`;
3906
3910
  const fields = [
@@ -3929,11 +3933,12 @@ ${ctx.requestId ?? "(none)"}` }
3929
3933
  type: "section",
3930
3934
  fields
3931
3935
  },
3932
- // Timestamp
3936
+ // Timestamp + suppressed count
3933
3937
  {
3934
3938
  type: "context",
3935
3939
  elements: [
3936
- { type: "mrkdwn", text: `*Time:* ${ctx.timestamp}` }
3940
+ { type: "mrkdwn", text: `*Time:* ${ctx.timestamp}` },
3941
+ ...suppressed > 0 ? [{ type: "mrkdwn", text: `_+${suppressed} suppressed since last notification_` }] : []
3937
3942
  ]
3938
3943
  },
3939
3944
  { type: "divider" },
@@ -3960,12 +3965,21 @@ ${ctx.requestId ?? "(none)"}` }
3960
3965
  return { text: title, blocks };
3961
3966
  }
3962
3967
  function createErrorSlackNotifier(options = {}) {
3963
- const { minStatusCode = 500 } = options;
3968
+ const { minStatusCode = 500, throttleMs = 6e4 } = options;
3964
3969
  return async (err, ctx) => {
3965
3970
  if (ctx.statusCode < minStatusCode) {
3966
3971
  return;
3967
3972
  }
3968
- const message = options.formatMessage?.(err, ctx) ?? defaultFormat(err, ctx);
3973
+ const key = throttleKey(err, ctx);
3974
+ const now = Date.now();
3975
+ const entry = throttleMap.get(key);
3976
+ if (entry && now - entry.lastSent < throttleMs) {
3977
+ entry.suppressed++;
3978
+ return;
3979
+ }
3980
+ const suppressed = entry?.suppressed ?? 0;
3981
+ throttleMap.set(key, { lastSent: now, suppressed: 0 });
3982
+ const message = options.formatMessage?.(err, ctx) ?? defaultFormat(err, ctx, suppressed);
3969
3983
  await sendSlack({
3970
3984
  ...message,
3971
3985
  webhookUrl: options.webhookUrl