logixia 1.13.0 → 2.0.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 (176) hide show
  1. package/README.md +81 -16
  2. package/dist/{async-context-CwBNMm2E.mjs → async-context-BzJrbfQs.mjs} +8 -4
  3. package/dist/async-context-BzJrbfQs.mjs.map +1 -0
  4. package/dist/{async-context-CSHWoHMB.js → async-context-DyQQYq3I.js} +8 -4
  5. package/dist/async-context-DyQQYq3I.js.map +1 -0
  6. package/dist/{azure-monitor.transport-CG7ClkYj.d.ts → azure-monitor.transport-Cn_3Usvu.d.ts} +2 -2
  7. package/dist/{azure-monitor.transport-CG7ClkYj.d.ts.map → azure-monitor.transport-Cn_3Usvu.d.ts.map} +1 -1
  8. package/dist/{azure-monitor.transport-Cm74kXZS.d.mts → azure-monitor.transport-D7Da4RAb.d.mts} +2 -2
  9. package/dist/{azure-monitor.transport-Cm74kXZS.d.mts.map → azure-monitor.transport-D7Da4RAb.d.mts.map} +1 -1
  10. package/dist/azure.d.mts +2 -2
  11. package/dist/azure.d.ts +2 -2
  12. package/dist/browser.d.mts +9 -0
  13. package/dist/browser.d.mts.map +1 -1
  14. package/dist/browser.d.ts +9 -0
  15. package/dist/browser.d.ts.map +1 -1
  16. package/dist/browser.js +43 -12
  17. package/dist/browser.js.map +1 -1
  18. package/dist/browser.mjs +43 -12
  19. package/dist/browser.mjs.map +1 -1
  20. package/dist/{logitron-logger.module-Bs3XYZgF.mjs → builder-0gEzRopq.mjs} +383 -1021
  21. package/dist/builder-0gEzRopq.mjs.map +1 -0
  22. package/dist/{logitron-logger.module-BsDZR5BH.js → builder-D44QktIo.js} +411 -1155
  23. package/dist/builder-D44QktIo.js.map +1 -0
  24. package/dist/cli/index.js +130 -65
  25. package/dist/cloudwatch.d.mts +2 -2
  26. package/dist/cloudwatch.d.ts +2 -2
  27. package/dist/cloudwatch.js +2 -1
  28. package/dist/cloudwatch.mjs +2 -1
  29. package/dist/{cloudwatch.transport-pNBNWJLt.js → cloudwatch.transport-BO1ahxw-.js} +26 -19
  30. package/dist/cloudwatch.transport-BO1ahxw-.js.map +1 -0
  31. package/dist/{cloudwatch.transport-yBwm4OeO.mjs → cloudwatch.transport-DZ6ies7g.mjs} +26 -19
  32. package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +1 -0
  33. package/dist/{cloudwatch.transport-CGHY4buE.d.mts → cloudwatch.transport-DsEq633o.d.ts} +9 -2
  34. package/dist/cloudwatch.transport-DsEq633o.d.ts.map +1 -0
  35. package/dist/{cloudwatch.transport-DK_8cKd3.d.ts → cloudwatch.transport-UlyZFWMn.d.mts} +9 -2
  36. package/dist/cloudwatch.transport-UlyZFWMn.d.mts.map +1 -0
  37. package/dist/{coerce.utils-BN980Y9V.js → coerce.utils-B-LilukN.js} +1 -1
  38. package/dist/{coerce.utils-BN980Y9V.js.map → coerce.utils-B-LilukN.js.map} +1 -1
  39. package/dist/{coerce.utils-Dxv4_ikC.mjs → coerce.utils-Cu6IUTgX.mjs} +1 -1
  40. package/dist/{coerce.utils-Dxv4_ikC.mjs.map → coerce.utils-Cu6IUTgX.mjs.map} +1 -1
  41. package/dist/correlation.d.mts +24 -6
  42. package/dist/correlation.d.mts.map +1 -1
  43. package/dist/correlation.d.ts +24 -6
  44. package/dist/correlation.d.ts.map +1 -1
  45. package/dist/correlation.js +41 -16
  46. package/dist/correlation.js.map +1 -1
  47. package/dist/correlation.mjs +41 -16
  48. package/dist/correlation.mjs.map +1 -1
  49. package/dist/entry-fields-BJkuPnIg.mjs +30 -0
  50. package/dist/entry-fields-BJkuPnIg.mjs.map +1 -0
  51. package/dist/entry-fields-BvOLFlJ4.js +36 -0
  52. package/dist/entry-fields-BvOLFlJ4.js.map +1 -0
  53. package/dist/gcp.d.mts +2 -2
  54. package/dist/gcp.d.ts +2 -2
  55. package/dist/gcp.js +2 -1
  56. package/dist/gcp.mjs +2 -1
  57. package/dist/{gcp.transport-BOhkVKlI.d.ts → gcp.transport-B7oc97sG.d.ts} +2 -2
  58. package/dist/gcp.transport-B7oc97sG.d.ts.map +1 -0
  59. package/dist/{gcp.transport-SHdtc1n0.js → gcp.transport-CHV8MGgz.js} +5 -5
  60. package/dist/gcp.transport-CHV8MGgz.js.map +1 -0
  61. package/dist/{gcp.transport-BJuNRFe_.mjs → gcp.transport-DBX5TPIy.mjs} +5 -5
  62. package/dist/gcp.transport-DBX5TPIy.mjs.map +1 -0
  63. package/dist/{gcp.transport-Bi6CUqZR.d.mts → gcp.transport-GrJurKo8.d.mts} +2 -2
  64. package/dist/gcp.transport-GrJurKo8.d.mts.map +1 -0
  65. package/dist/{index-BPKwwERo.d.mts → index-3mw5gaCw.d.mts} +2 -2
  66. package/dist/{index-BPKwwERo.d.mts.map → index-3mw5gaCw.d.mts.map} +1 -1
  67. package/dist/{index-C1X-TE5I.d.ts → index-DhIoploR.d.ts} +2 -2
  68. package/dist/{index-C1X-TE5I.d.ts.map → index-DhIoploR.d.ts.map} +1 -1
  69. package/dist/index.d.mts +99 -137
  70. package/dist/index.d.mts.map +1 -1
  71. package/dist/index.d.ts +99 -137
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +146 -510
  74. package/dist/index.js.map +1 -1
  75. package/dist/index.mjs +99 -431
  76. package/dist/index.mjs.map +1 -1
  77. package/dist/logitron-logger-BFDSO8Io.d.mts +257 -0
  78. package/dist/logitron-logger-BFDSO8Io.d.mts.map +1 -0
  79. package/dist/logitron-logger-UVeN-mQm.d.ts +257 -0
  80. package/dist/logitron-logger-UVeN-mQm.d.ts.map +1 -0
  81. package/dist/middleware.d.mts +11 -4
  82. package/dist/middleware.d.mts.map +1 -1
  83. package/dist/middleware.d.ts +11 -4
  84. package/dist/middleware.d.ts.map +1 -1
  85. package/dist/middleware.js +148 -24
  86. package/dist/middleware.js.map +1 -1
  87. package/dist/middleware.mjs +148 -24
  88. package/dist/middleware.mjs.map +1 -1
  89. package/dist/nest.d.mts +481 -6
  90. package/dist/nest.d.mts.map +1 -1
  91. package/dist/nest.d.ts +481 -6
  92. package/dist/nest.d.ts.map +1 -1
  93. package/dist/nest.js +884 -13
  94. package/dist/nest.js.map +1 -0
  95. package/dist/nest.mjs +863 -6
  96. package/dist/nest.mjs.map +1 -0
  97. package/dist/{search-CFQRISUv.mjs → search-BFbu_oVn.mjs} +35 -12
  98. package/dist/search-BFbu_oVn.mjs.map +1 -0
  99. package/dist/{search-njTI2hdo.js → search-C4V0OMEn.js} +35 -12
  100. package/dist/search-C4V0OMEn.js.map +1 -0
  101. package/dist/{search-DujhR94O.d.ts → search-CNh9uVwR.d.ts} +2 -3
  102. package/dist/search-CNh9uVwR.d.ts.map +1 -0
  103. package/dist/{search-CQOo-Iq6.d.mts → search-DXANsQ2n.d.mts} +2 -3
  104. package/dist/search-DXANsQ2n.d.mts.map +1 -0
  105. package/dist/search.d.mts +3 -3
  106. package/dist/search.d.ts +3 -3
  107. package/dist/search.js +2 -2
  108. package/dist/search.mjs +2 -2
  109. package/dist/slack.transport-BZzoXDPn.d.mts +112 -0
  110. package/dist/slack.transport-BZzoXDPn.d.mts.map +1 -0
  111. package/dist/slack.transport-BfJRouB2.js +654 -0
  112. package/dist/slack.transport-BfJRouB2.js.map +1 -0
  113. package/dist/slack.transport-Bzw7sucI.mjs +643 -0
  114. package/dist/slack.transport-Bzw7sucI.mjs.map +1 -0
  115. package/dist/slack.transport-CXjQYOes.d.ts +112 -0
  116. package/dist/slack.transport-CXjQYOes.d.ts.map +1 -0
  117. package/dist/testing.d.mts +2 -2
  118. package/dist/testing.d.ts +2 -2
  119. package/dist/trace.utils-CvXgaiQT.mjs +278 -0
  120. package/dist/trace.utils-CvXgaiQT.mjs.map +1 -0
  121. package/dist/trace.utils-DpBOLDtq.js +358 -0
  122. package/dist/trace.utils-DpBOLDtq.js.map +1 -0
  123. package/dist/{transport.manager-D6KL3t9V.mjs → transport.manager-BiuCAJd2.mjs} +526 -160
  124. package/dist/transport.manager-BiuCAJd2.mjs.map +1 -0
  125. package/dist/{transport.manager-BivyUTtU.js → transport.manager-CXbKKdVE.js} +537 -159
  126. package/dist/transport.manager-CXbKKdVE.js.map +1 -0
  127. package/dist/{transport.types-B5vcyv2k.d.mts → transport.types-BMnurPcc.d.mts} +23 -3
  128. package/dist/transport.types-BMnurPcc.d.mts.map +1 -0
  129. package/dist/{transport.types-BuY84TwO.d.ts → transport.types-CGoHpWQR.d.ts} +23 -3
  130. package/dist/transport.types-CGoHpWQR.d.ts.map +1 -0
  131. package/dist/transports.d.mts +50 -20
  132. package/dist/transports.d.mts.map +1 -1
  133. package/dist/transports.d.ts +50 -20
  134. package/dist/transports.d.ts.map +1 -1
  135. package/dist/transports.js +9 -5
  136. package/dist/transports.mjs +8 -6
  137. package/dist/worker-entry.js +307 -55
  138. package/dist/worker.d.mts +2 -2
  139. package/dist/worker.d.ts +2 -2
  140. package/dist/worker.js +1 -1
  141. package/dist/worker.mjs +1 -1
  142. package/dist/{worker.transport-CfH6z37Z.js → worker.transport-BdPxu3PN.js} +1 -1
  143. package/dist/{worker.transport-CfH6z37Z.js.map → worker.transport-BdPxu3PN.js.map} +1 -1
  144. package/dist/{worker.transport-mKXgNK0L.d.ts → worker.transport-CdzSZJmw.d.ts} +2 -2
  145. package/dist/{worker.transport-mKXgNK0L.d.ts.map → worker.transport-CdzSZJmw.d.ts.map} +1 -1
  146. package/dist/{worker.transport-BP8Q28Z2.d.mts → worker.transport-JvGPog3C.d.mts} +2 -2
  147. package/dist/{worker.transport-BP8Q28Z2.d.mts.map → worker.transport-JvGPog3C.d.mts.map} +1 -1
  148. package/dist/{worker.transport-BhMyvqQ2.mjs → worker.transport-lPxO2MVL.mjs} +1 -1
  149. package/dist/{worker.transport-BhMyvqQ2.mjs.map → worker.transport-lPxO2MVL.mjs.map} +1 -1
  150. package/package.json +27 -14
  151. package/dist/.tsbuildinfo +0 -1
  152. package/dist/async-context-CSHWoHMB.js.map +0 -1
  153. package/dist/async-context-CwBNMm2E.mjs.map +0 -1
  154. package/dist/cloudwatch.transport-CGHY4buE.d.mts.map +0 -1
  155. package/dist/cloudwatch.transport-DK_8cKd3.d.ts.map +0 -1
  156. package/dist/cloudwatch.transport-pNBNWJLt.js.map +0 -1
  157. package/dist/cloudwatch.transport-yBwm4OeO.mjs.map +0 -1
  158. package/dist/gcp.transport-BJuNRFe_.mjs.map +0 -1
  159. package/dist/gcp.transport-BOhkVKlI.d.ts.map +0 -1
  160. package/dist/gcp.transport-Bi6CUqZR.d.mts.map +0 -1
  161. package/dist/gcp.transport-SHdtc1n0.js.map +0 -1
  162. package/dist/logitron-logger.module-BdG1Jnpk.d.mts +0 -618
  163. package/dist/logitron-logger.module-BdG1Jnpk.d.mts.map +0 -1
  164. package/dist/logitron-logger.module-Bs3XYZgF.mjs.map +0 -1
  165. package/dist/logitron-logger.module-BsDZR5BH.js.map +0 -1
  166. package/dist/logitron-logger.module-CbXy2gfW.d.ts +0 -618
  167. package/dist/logitron-logger.module-CbXy2gfW.d.ts.map +0 -1
  168. package/dist/search-CFQRISUv.mjs.map +0 -1
  169. package/dist/search-CQOo-Iq6.d.mts.map +0 -1
  170. package/dist/search-DujhR94O.d.ts.map +0 -1
  171. package/dist/search-njTI2hdo.js.map +0 -1
  172. package/dist/transport.manager-BivyUTtU.js.map +0 -1
  173. package/dist/transport.manager-D6KL3t9V.mjs.map +0 -1
  174. package/dist/transport.types-B5vcyv2k.d.mts.map +0 -1
  175. package/dist/transport.types-BuY84TwO.d.ts.map +0 -1
  176. package/scripts/postinstall.js +0 -61
@@ -0,0 +1,643 @@
1
+ import { r as internalWarn, t as internalError } from "./internal-log-Cm8SOWSV.mjs";
2
+ import { i as stringifyForLog } from "./safe-stringify-DJ66tW5I.mjs";
3
+ import { i as HttpError, o as isPermanentHttpError } from "./batch-buffer-PGvSgbmd.mjs";
4
+ import { l as BufferCap } from "./transport.manager-BiuCAJd2.mjs";
5
+
6
+ //#region src/transports/webhook-queue.ts
7
+ const MAX_BACKOFF_MS = 3e4;
8
+ const DEFAULT_QUEUE_CAP = 1e3;
9
+ const CLOSE_TIMEOUT_MS = 3e3;
10
+ const OVERFLOW_WARN_MS = 5e3;
11
+ const DEFAULT_WEBHOOK_TIMEOUT_MS = 1e4;
12
+ const ZWSP = "​";
13
+ const LEVEL_RANK = {
14
+ fatal: 0,
15
+ error: 1,
16
+ warn: 2,
17
+ warning: 2,
18
+ info: 3,
19
+ debug: 4,
20
+ verbose: 5,
21
+ trace: 6
22
+ };
23
+ function resolveMinLevel(level, label) {
24
+ const resolved = (level ?? "error").toLowerCase();
25
+ if (!(resolved in LEVEL_RANK)) throw new TypeError(`${label}: unknown level "${String(level).slice(0, 40)}"`);
26
+ return resolved;
27
+ }
28
+ /** Unknown (custom) entry levels pass, like the TransportManager does. */
29
+ function meetsLevel(entryLevel, minLevel) {
30
+ const entryRank = LEVEL_RANK[entryLevel.toLowerCase()];
31
+ return entryRank === void 0 || entryRank <= (LEVEL_RANK[minLevel] ?? 1);
32
+ }
33
+ /** Validates the secret URL. Error messages never contain it. */
34
+ function parseWebhookUrl(raw, rules) {
35
+ const { label, allowCustomUrl, hostMatches, pathMatches, expected } = rules;
36
+ if (typeof raw !== "string" || raw.length === 0) throw new TypeError(`${label}: webhookUrl is required`);
37
+ let url;
38
+ try {
39
+ url = new URL(raw);
40
+ } catch {
41
+ throw new TypeError(`${label}: webhookUrl is not a valid URL`);
42
+ }
43
+ if (url.protocol !== "https:") throw new TypeError(`${label}: webhookUrl must use https:`);
44
+ if (url.username || url.password) throw new TypeError(`${label}: webhookUrl must not contain credentials`);
45
+ if (!allowCustomUrl) {
46
+ if (!hostMatches(url.hostname.toLowerCase()) || !pathMatches(url.pathname) || url.port) throw new TypeError(`${label}: webhookUrl must be a ${expected} webhook (set allowCustomUrl: true to use another https endpoint)`);
47
+ }
48
+ return url;
49
+ }
50
+ function retryAfterHeaderMs(res) {
51
+ var _res$headers;
52
+ const raw = (_res$headers = res.headers) === null || _res$headers === void 0 ? void 0 : _res$headers.get("retry-after");
53
+ if (raw === null || raw === void 0 || raw === "") return void 0;
54
+ const seconds = Number(raw);
55
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1e3 : void 0;
56
+ }
57
+ function nonNegative(value, fallback) {
58
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
59
+ }
60
+ function failureCode(err) {
61
+ const cause = err === null || err === void 0 ? void 0 : err.cause;
62
+ const code = (cause === null || cause === void 0 ? void 0 : cause.code) ?? (err === null || err === void 0 ? void 0 : err.code);
63
+ if (typeof code === "string") return code.slice(0, 40);
64
+ return err instanceof Error ? err.name : "error";
65
+ }
66
+ function sleep(ms, signal) {
67
+ return new Promise((resolve) => {
68
+ var _timer$unref;
69
+ if (signal.aborted) return resolve();
70
+ const done = () => {
71
+ clearTimeout(timer);
72
+ signal.removeEventListener("abort", done);
73
+ resolve();
74
+ };
75
+ const timer = setTimeout(done, ms);
76
+ (_timer$unref = timer.unref) === null || _timer$unref === void 0 || _timer$unref.call(timer);
77
+ signal.addEventListener("abort", done, { once: true });
78
+ });
79
+ }
80
+ async function cancelBody(res) {
81
+ try {
82
+ var _res$body;
83
+ await ((_res$body = res.body) === null || _res$body === void 0 ? void 0 : _res$body.cancel());
84
+ } catch {}
85
+ }
86
+ /**
87
+ * Throttled queue for chat webhooks. It is not built on BatchBuffer because the
88
+ * semantics differ: sends are spaced by `minIntervalMs` (leading edge, then one
89
+ * message per window), a window sends one summarized message and drops the
90
+ * overflow, 429 waits are cancellable by close(), and flush() surfaces send
91
+ * errors. HttpError / isPermanentHttpError are shared with BatchBuffer.
92
+ *
93
+ * Entries past `maxPerMessage` in one window are not sent: the message carries a
94
+ * "… and N more" line and an internal warning reports the count.
95
+ */
96
+ var WebhookQueue = class {
97
+ constructor(opts) {
98
+ this.opts = opts;
99
+ this.items = [];
100
+ this.timer = null;
101
+ this.nextAllowed = 0;
102
+ this.failures = 0;
103
+ this.closed = false;
104
+ this.shutdown = new AbortController();
105
+ this.overflowPending = 0;
106
+ this.lastOverflowWarn = 0;
107
+ this.fetchWarned = false;
108
+ this.host = opts.url.hostname;
109
+ this.httpLabel = `${opts.label} (${this.host})`;
110
+ this.minIntervalMs = nonNegative(opts.minIntervalMs, 1e3);
111
+ this.maxRetries = Math.floor(nonNegative(opts.maxRetries, 1));
112
+ this.timeoutMs = nonNegative(opts.requestTimeoutMs, DEFAULT_WEBHOOK_TIMEOUT_MS);
113
+ this.cap = new BufferCap(opts.label, opts.maxQueueSize, opts.maxPerMessage, DEFAULT_QUEUE_CAP);
114
+ }
115
+ get length() {
116
+ return this.items.length;
117
+ }
118
+ push(entry) {
119
+ if (this.closed) return;
120
+ this.items.push(entry);
121
+ this.cap.enforce(this.items);
122
+ this.schedule();
123
+ }
124
+ flush() {
125
+ this.inFlight ??= this.drain().finally(() => {
126
+ this.inFlight = void 0;
127
+ });
128
+ return this.inFlight;
129
+ }
130
+ /** Cancels rate-limit waits, then makes one best-effort send with a short timeout. */
131
+ async close() {
132
+ this.closed = true;
133
+ this.clearTimer();
134
+ this.shutdown.abort();
135
+ try {
136
+ await this.inFlight;
137
+ } catch {}
138
+ try {
139
+ await this.flush();
140
+ } catch (err) {
141
+ internalError(`${this.opts.label} flush on close failed`, err);
142
+ }
143
+ this.reportOverflow(true);
144
+ if (this.items.length > 0) {
145
+ internalError(`${this.opts.label} closing with ${this.items.length} undelivered entries`);
146
+ this.items = [];
147
+ }
148
+ }
149
+ clearTimer() {
150
+ if (this.timer) clearTimeout(this.timer);
151
+ this.timer = null;
152
+ }
153
+ schedule() {
154
+ var _this$timer$unref, _this$timer;
155
+ if (this.timer || this.closed || this.items.length === 0) return;
156
+ const delay = Math.max(0, this.nextAllowed - Date.now());
157
+ this.timer = setTimeout(() => {
158
+ this.timer = null;
159
+ this.flush().catch((err) => internalError(`${this.opts.label} send failed`, err));
160
+ }, delay);
161
+ (_this$timer$unref = (_this$timer = this.timer).unref) === null || _this$timer$unref === void 0 || _this$timer$unref.call(_this$timer);
162
+ }
163
+ reportOverflow(force = false) {
164
+ const now = Date.now();
165
+ if (this.overflowPending === 0 || !force && now - this.lastOverflowWarn < OVERFLOW_WARN_MS) return;
166
+ const n = this.overflowPending;
167
+ internalWarn(`${this.opts.label}: ${n} entr${n === 1 ? "y" : "ies"} over the per-message limit (${this.opts.maxPerMessage}) summarized in a "... and N more" line and not sent`);
168
+ this.overflowPending = 0;
169
+ this.lastOverflowWarn = now;
170
+ }
171
+ render(batch) {
172
+ const rendered = [];
173
+ let bad = 0;
174
+ for (const entry of batch) try {
175
+ const value = this.opts.render(entry);
176
+ JSON.stringify(value);
177
+ rendered.push(value);
178
+ } catch (err) {
179
+ bad += 1;
180
+ if (bad === 1) internalError(`${this.opts.label}: dropping an entry that cannot be rendered`, err);
181
+ }
182
+ if (bad > 1) internalError(`${this.opts.label}: dropped ${bad} entries that cannot be rendered`);
183
+ return rendered;
184
+ }
185
+ async drain() {
186
+ if (this.items.length === 0) return;
187
+ if (typeof fetch !== "function") {
188
+ if (!this.fetchWarned) {
189
+ this.fetchWarned = true;
190
+ internalWarn(`${this.opts.label}: global fetch is unavailable (Node 18+ required); dropping entries`);
191
+ }
192
+ this.items = [];
193
+ return;
194
+ }
195
+ const all = this.items;
196
+ this.items = [];
197
+ const batch = all.slice(0, this.opts.maxPerMessage);
198
+ const overflow = all.length - batch.length;
199
+ if (overflow > 0) {
200
+ this.overflowPending += overflow;
201
+ this.reportOverflow();
202
+ }
203
+ this.nextAllowed = Date.now() + this.minIntervalMs;
204
+ try {
205
+ const rendered = this.render(batch);
206
+ if (rendered.length === 0 && overflow === 0) return;
207
+ let body;
208
+ try {
209
+ body = JSON.stringify(this.opts.assemble(rendered, overflow));
210
+ } catch (err) {
211
+ internalError(`${this.opts.label}: dropping a batch that cannot be serialized`, err);
212
+ return;
213
+ }
214
+ try {
215
+ await this.sendWithRetry(body);
216
+ this.failures = 0;
217
+ } catch (err) {
218
+ if (!isPermanentHttpError(err)) {
219
+ this.items.unshift(...batch);
220
+ this.cap.enforce(this.items);
221
+ this.failures += 1;
222
+ const backoff = Math.min(MAX_BACKOFF_MS, Math.max(1e3, this.minIntervalMs) * 2 ** this.failures);
223
+ this.nextAllowed = Math.max(this.nextAllowed, Date.now() + backoff);
224
+ }
225
+ throw err;
226
+ }
227
+ } finally {
228
+ this.schedule();
229
+ }
230
+ }
231
+ async sendWithRetry(body) {
232
+ for (let attempt = 0;; attempt += 1) {
233
+ const res = await this.post(body);
234
+ if (res.ok) return;
235
+ if (res.status !== 429) throw new HttpError(res.status, "", this.httpLabel);
236
+ const waitMs = Math.min(MAX_BACKOFF_MS, Math.max(0, res.retryAfterMs ?? Math.max(1e3, this.minIntervalMs)));
237
+ this.nextAllowed = Math.max(this.nextAllowed, Date.now() + waitMs);
238
+ if (attempt >= this.maxRetries || this.closed) throw new HttpError(429, "", this.httpLabel);
239
+ await sleep(waitMs, this.shutdown.signal);
240
+ if (this.shutdown.signal.aborted) throw new Error(`${this.opts.label}: rate-limit wait cancelled by close()`);
241
+ }
242
+ }
243
+ async readRetryAfter(res) {
244
+ try {
245
+ return this.opts.retryAfterMs ? await this.opts.retryAfterMs(res) : retryAfterHeaderMs(res);
246
+ } catch {
247
+ return;
248
+ }
249
+ }
250
+ async post(body) {
251
+ const controller = new AbortController();
252
+ const timeoutMs = this.closed ? Math.min(this.timeoutMs, CLOSE_TIMEOUT_MS) : this.timeoutMs;
253
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
254
+ try {
255
+ const res = await fetch(this.opts.url, {
256
+ method: "POST",
257
+ headers: { "Content-Type": "application/json" },
258
+ body,
259
+ redirect: "error",
260
+ signal: controller.signal
261
+ });
262
+ let retryAfterMs;
263
+ try {
264
+ if (res.status === 429) retryAfterMs = await this.readRetryAfter(res);
265
+ } finally {
266
+ await cancelBody(res);
267
+ }
268
+ return {
269
+ ok: res.ok,
270
+ status: res.status,
271
+ retryAfterMs
272
+ };
273
+ } catch (err) {
274
+ throw new Error(`${this.opts.label}: request to ${this.host} failed (${controller.signal.aborted ? "timeout" : failureCode(err)})`);
275
+ } finally {
276
+ clearTimeout(timer);
277
+ }
278
+ }
279
+ };
280
+ /** Cuts `text` to `max` chars without splitting a surrogate pair or a `&amp;`-style entity. */
281
+ function truncateText(text, max, ellipsis = "…") {
282
+ if (text.length <= max) return text;
283
+ let cut = text.slice(0, Math.max(0, max - ellipsis.length));
284
+ cut = cut.replace(/&[a-z]{0,3}$/, "");
285
+ if (/[\uD800-\uDBFF]$/.test(cut)) cut = cut.slice(0, -1);
286
+ return cut + ellipsis;
287
+ }
288
+ /** Breaks code-fence and broadcast-mention sequences with a zero-width space. */
289
+ function defuseText(text) {
290
+ return text.replace(/`{3,}/g, (m) => m.split("").join(ZWSP)).replace(/@(everyone|here|channel)/gi, `@${ZWSP}$1`);
291
+ }
292
+ /** Collapses control characters and newlines for single-line fields. */
293
+ function singleLine(text) {
294
+ return text.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
295
+ }
296
+ /** ISO string, or undefined for a missing or invalid timestamp. */
297
+ function safeIso(date) {
298
+ return date instanceof Date && !Number.isNaN(date.getTime()) ? date.toISOString() : void 0;
299
+ }
300
+
301
+ //#endregion
302
+ //#region src/transports/discord.transport.ts
303
+ const DISCORD_TITLE_MAX = 256;
304
+ const DISCORD_DESCRIPTION_MAX = 4096;
305
+ const DISCORD_FIELD_VALUE_MAX = 1024;
306
+ const DISCORD_EMBEDS_MAX = 10;
307
+ const DISCORD_FIELDS_MAX = 25;
308
+ const DISCORD_EMBED_TOTAL_MAX = 6e3;
309
+ const DISCORD_CONTENT_MAX = 2e3;
310
+ const FIELD_VALUE_CAP = 64;
311
+ const DISCORD_FOOTER_MAX = 2048;
312
+ const FENCE_OVERHEAD = 12;
313
+ const MIN_USEFUL_BODY = 20;
314
+ const DISCORD_LEVEL_COLORS = {
315
+ fatal: 10038562,
316
+ error: 15548997,
317
+ warn: 16705372,
318
+ warning: 16705372,
319
+ info: 3447003,
320
+ debug: 9807270,
321
+ verbose: 9807270,
322
+ trace: 9807270
323
+ };
324
+ const DEFAULT_COLOR = 9807270;
325
+ /** Escapes Discord markdown and defuses @everyone / @here and code fences. */
326
+ function escapeDiscord(text) {
327
+ return defuseText(text.replace(/[\\*_~`|>#[\]()]/g, "\\$&"));
328
+ }
329
+ function textLength(value, key) {
330
+ const text = value === null || value === void 0 ? void 0 : value[key];
331
+ return typeof text === "string" ? text.length : 0;
332
+ }
333
+ /** Counts every text field Discord counts toward the 6000-character limit. */
334
+ function embedChars(embed) {
335
+ var _embed$title, _embed$description;
336
+ return (((_embed$title = embed.title) === null || _embed$title === void 0 ? void 0 : _embed$title.length) ?? 0) + (((_embed$description = embed.description) === null || _embed$description === void 0 ? void 0 : _embed$description.length) ?? 0) + (embed.fields ?? []).reduce((n, f) => n + f.name.length + f.value.length, 0) + textLength(embed["footer"], "text") + textLength(embed["author"], "name");
337
+ }
338
+ /** Shrinks one embed to `limit` chars: description first, then fields, footer/author, title. */
339
+ function shrinkToFit(embed, limit) {
340
+ var _embed$fields;
341
+ const excess = () => embedChars(embed) - limit;
342
+ if (excess() > 0 && embed.description) {
343
+ const keep = embed.description.length - excess();
344
+ if (keep > 0) embed.description = truncateText(embed.description, keep);
345
+ else delete embed.description;
346
+ }
347
+ while (excess() > 0 && ((_embed$fields = embed.fields) === null || _embed$fields === void 0 ? void 0 : _embed$fields.length)) embed.fields.pop();
348
+ if (excess() > 0) {
349
+ delete embed["footer"];
350
+ delete embed["author"];
351
+ }
352
+ if (excess() > 0 && embed.title) embed.title = truncateText(embed.title, Math.max(1, embed.title.length - excess()));
353
+ }
354
+ function codeBlock(json, max) {
355
+ return `\`\`\`json\n${truncateText(json.replace(/`{3,}/g, (m) => m.split("").join("​")), max)}\n\`\`\``;
356
+ }
357
+ function clampCustomEmbed(embed) {
358
+ const out = { ...embed };
359
+ if (typeof out.title === "string") out.title = truncateText(out.title, DISCORD_TITLE_MAX);
360
+ if (typeof out.description === "string") out.description = truncateText(out.description, DISCORD_DESCRIPTION_MAX);
361
+ if (Array.isArray(out.fields)) out.fields = out.fields.slice(0, DISCORD_FIELDS_MAX).map((f) => ({
362
+ ...f,
363
+ name: truncateText(String(f.name), DISCORD_TITLE_MAX),
364
+ value: truncateText(String(f.value), DISCORD_FIELD_VALUE_MAX)
365
+ }));
366
+ const footer = out["footer"];
367
+ if (footer && typeof footer.text === "string") out["footer"] = {
368
+ ...footer,
369
+ text: truncateText(footer.text, DISCORD_FOOTER_MAX)
370
+ };
371
+ const author = out["author"];
372
+ if (author && typeof author.name === "string") out["author"] = {
373
+ ...author,
374
+ name: truncateText(author.name, DISCORD_TITLE_MAX)
375
+ };
376
+ return out;
377
+ }
378
+ var DiscordTransport = class {
379
+ constructor(config) {
380
+ this.name = "discord";
381
+ const url = parseWebhookUrl(config.webhookUrl, {
382
+ label: "DiscordTransport",
383
+ hostMatches: (h) => [
384
+ "discord.com",
385
+ "discordapp.com",
386
+ "ptb.discord.com",
387
+ "canary.discord.com"
388
+ ].includes(h),
389
+ pathMatches: (p) => /^\/api(\/v\d+)?\/webhooks\//.test(p),
390
+ expected: "discord.com",
391
+ allowCustomUrl: config.allowCustomUrl
392
+ });
393
+ if (config.avatarUrl !== void 0 && !/^https:\/\//i.test(config.avatarUrl)) throw new TypeError("DiscordTransport: avatarUrl must use https:");
394
+ this.level = resolveMinLevel(config.level, "DiscordTransport");
395
+ if (config.filter) this.filter = config.filter;
396
+ this.username = config.username;
397
+ this.avatarUrl = config.avatarUrl;
398
+ this.formatter = config.formatter;
399
+ const queue = new WebhookQueue({
400
+ label: "DiscordTransport",
401
+ url,
402
+ minIntervalMs: config.minIntervalMs,
403
+ maxPerMessage: DISCORD_EMBEDS_MAX,
404
+ maxQueueSize: config.maxBufferSize,
405
+ maxRetries: config.maxRetries,
406
+ requestTimeoutMs: config.requestTimeoutMs ?? DEFAULT_WEBHOOK_TIMEOUT_MS,
407
+ render: (entry) => this.draftFor(entry),
408
+ assemble: (drafts, overflow) => this.buildPayload(drafts, overflow),
409
+ retryAfterMs: readRetryAfter
410
+ });
411
+ Object.defineProperty(this, "queue", {
412
+ value: queue,
413
+ enumerable: false
414
+ });
415
+ }
416
+ write(entry) {
417
+ if (!meetsLevel(entry.level, this.level)) return;
418
+ this.queue.push(entry);
419
+ }
420
+ flush() {
421
+ return this.queue.flush();
422
+ }
423
+ close() {
424
+ return this.queue.close();
425
+ }
426
+ /** Never serialise the webhook URL (JSON.stringify, util.inspect). */
427
+ toJSON() {
428
+ return {
429
+ name: this.name,
430
+ level: this.level
431
+ };
432
+ }
433
+ buildPayload(drafts, overflow) {
434
+ const kept = this.fitToBudget(drafts);
435
+ const dropped = overflow + (drafts.length - kept.length);
436
+ const payload = {
437
+ embeds: kept.map((d) => d.embed),
438
+ allowed_mentions: { parse: [] }
439
+ };
440
+ if (dropped > 0) payload["content"] = truncateText(`… and ${dropped} more entr${dropped === 1 ? "y" : "ies"} not shown`, DISCORD_CONTENT_MAX);
441
+ if (this.username) payload["username"] = truncateText(this.username, 80);
442
+ if (this.avatarUrl) payload["avatar_url"] = this.avatarUrl;
443
+ return payload;
444
+ }
445
+ /** Enforces the 6000-char total: drops trailing embeds if needed, then shares the rest among descriptions. */
446
+ fitToBudget(drafts) {
447
+ const kept = [...drafts];
448
+ const base = () => kept.reduce((n, d) => n + embedChars(d.embed), 0);
449
+ while (kept.length > 1 && base() > DISCORD_EMBED_TOTAL_MAX) kept.pop();
450
+ const only = kept[0];
451
+ if (only && base() > DISCORD_EMBED_TOTAL_MAX) shrinkToFit(only.embed, DISCORD_EMBED_TOTAL_MAX);
452
+ const withData = kept.filter((d) => d.json !== void 0);
453
+ if (withData.length === 0) return kept;
454
+ const share = Math.floor((DISCORD_EMBED_TOTAL_MAX - base()) / withData.length);
455
+ const body = Math.min(DISCORD_DESCRIPTION_MAX, share) - FENCE_OVERHEAD;
456
+ for (const d of withData) if (body >= MIN_USEFUL_BODY) d.embed.description = codeBlock(d.json, body);
457
+ return kept;
458
+ }
459
+ draftFor(entry) {
460
+ if (this.formatter) try {
461
+ return {
462
+ embed: clampCustomEmbed(this.formatter(entry)),
463
+ json: void 0
464
+ };
465
+ } catch {
466
+ internalWarn("DiscordTransport: formatter threw; using the default embed");
467
+ }
468
+ return defaultDraft(entry);
469
+ }
470
+ };
471
+ function defaultDraft(entry) {
472
+ const title = `[${singleLine(entry.level).toUpperCase()}] ${escapeDiscord(singleLine(entry.message))}`;
473
+ const fields = [
474
+ ["appName", entry.appName],
475
+ ["environment", entry.environment],
476
+ ["context", entry.context],
477
+ ["traceId", entry.traceId]
478
+ ].map(([name, value]) => [name, typeof value === "string" ? singleLine(value) : ""]).filter(([, value]) => value !== "").map(([name, value]) => ({
479
+ name,
480
+ value: truncateText(escapeDiscord(value), FIELD_VALUE_CAP),
481
+ inline: true
482
+ }));
483
+ const embed = {
484
+ title: truncateText(title, DISCORD_TITLE_MAX),
485
+ color: DISCORD_LEVEL_COLORS[entry.level.toLowerCase()] ?? DEFAULT_COLOR,
486
+ fields
487
+ };
488
+ const timestamp = safeIso(entry.timestamp);
489
+ if (timestamp) embed.timestamp = timestamp;
490
+ return {
491
+ embed,
492
+ json: entry.data && Object.keys(entry.data).length > 0 ? stringifyForLog(entry.data) : void 0
493
+ };
494
+ }
495
+ async function readRetryAfter(res) {
496
+ try {
497
+ var _res$json;
498
+ const body = await ((_res$json = res.json) === null || _res$json === void 0 ? void 0 : _res$json.call(res));
499
+ const seconds = Number(body === null || body === void 0 ? void 0 : body.retry_after);
500
+ if ((body === null || body === void 0 ? void 0 : body.retry_after) !== void 0 && Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
501
+ } catch {}
502
+ return retryAfterHeaderMs(res);
503
+ }
504
+
505
+ //#endregion
506
+ //#region src/transports/slack.transport.ts
507
+ const SLACK_HEADER_MAX = 150;
508
+ const SLACK_DATA_MAX = 2500;
509
+ const SLACK_FIELD_MAX = 300;
510
+ const SLACK_TEXT_MAX = 1e3;
511
+ const SLACK_MAX_BLOCKS = 50;
512
+ const SLACK_MAX_PER_MESSAGE = 10;
513
+ /** Escapes the three characters Slack treats specially and defuses broadcast mentions. */
514
+ function escapeSlack(text) {
515
+ return defuseText(text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
516
+ }
517
+ const LEVEL_EMOJI = {
518
+ fatal: ":rotating_light:",
519
+ error: ":red_circle:",
520
+ warn: ":warning:",
521
+ warning: ":warning:",
522
+ info: ":information_source:"
523
+ };
524
+ var SlackTransport = class {
525
+ constructor(config) {
526
+ this.name = "slack";
527
+ const url = parseWebhookUrl(config.webhookUrl, {
528
+ label: "SlackTransport",
529
+ hostMatches: (h) => h === "hooks.slack.com" || h === "hooks.slack-gov.com",
530
+ pathMatches: (p) => p.startsWith("/services/") || p.startsWith("/triggers/"),
531
+ expected: "hooks.slack.com",
532
+ allowCustomUrl: config.allowCustomUrl
533
+ });
534
+ this.level = resolveMinLevel(config.level, "SlackTransport");
535
+ if (config.filter) this.filter = config.filter;
536
+ this.username = config.username;
537
+ this.iconEmoji = config.iconEmoji;
538
+ this.formatter = config.formatter;
539
+ const queue = new WebhookQueue({
540
+ label: "SlackTransport",
541
+ url,
542
+ minIntervalMs: config.minIntervalMs,
543
+ maxPerMessage: SLACK_MAX_PER_MESSAGE,
544
+ maxQueueSize: config.maxBufferSize,
545
+ maxRetries: config.maxRetries,
546
+ requestTimeoutMs: config.requestTimeoutMs ?? DEFAULT_WEBHOOK_TIMEOUT_MS,
547
+ render: (entry) => this.messageFor(entry),
548
+ assemble: (messages, overflow) => this.buildPayload(messages, overflow)
549
+ });
550
+ Object.defineProperty(this, "queue", {
551
+ value: queue,
552
+ enumerable: false
553
+ });
554
+ }
555
+ write(entry) {
556
+ if (!meetsLevel(entry.level, this.level)) return;
557
+ this.queue.push(entry);
558
+ }
559
+ flush() {
560
+ return this.queue.flush();
561
+ }
562
+ close() {
563
+ return this.queue.close();
564
+ }
565
+ /** Never serialise the webhook URL (JSON.stringify, util.inspect). */
566
+ toJSON() {
567
+ return {
568
+ name: this.name,
569
+ level: this.level
570
+ };
571
+ }
572
+ buildPayload(messages, overflow) {
573
+ const blocks = messages.flatMap((m) => m.blocks ?? []);
574
+ const counter = overflow > 0 ? `… and ${overflow} more entr${overflow === 1 ? "y" : "ies"} not shown` : "";
575
+ if (counter) blocks.push({
576
+ type: "context",
577
+ elements: [{
578
+ type: "mrkdwn",
579
+ text: counter
580
+ }]
581
+ });
582
+ const payload = {
583
+ text: truncateText([...messages.map((m) => m.text ?? ""), counter].filter(Boolean).join("\n"), SLACK_TEXT_MAX),
584
+ blocks: blocks.slice(0, SLACK_MAX_BLOCKS)
585
+ };
586
+ if (this.username) payload["username"] = truncateText(this.username, 80);
587
+ if (this.iconEmoji) payload["icon_emoji"] = this.iconEmoji;
588
+ return payload;
589
+ }
590
+ messageFor(entry) {
591
+ if (this.formatter) try {
592
+ return this.formatter(entry);
593
+ } catch {
594
+ internalWarn("SlackTransport: formatter threw; using the default message");
595
+ }
596
+ return defaultMessage(entry);
597
+ }
598
+ };
599
+ function defaultMessage(entry) {
600
+ const level = singleLine(entry.level).toUpperCase();
601
+ const emoji = LEVEL_EMOJI[entry.level.toLowerCase()] ?? ":white_circle:";
602
+ const headline = `[${level}] ${singleLine(entry.message)}`;
603
+ const blocks = [{
604
+ type: "header",
605
+ text: {
606
+ type: "plain_text",
607
+ text: truncateText(escapeSlack(headline), SLACK_HEADER_MAX),
608
+ emoji: false
609
+ }
610
+ }];
611
+ const context = [
612
+ ["app", entry.appName],
613
+ ["env", entry.environment],
614
+ ["context", entry.context],
615
+ ["trace", entry.traceId],
616
+ ["time", safeIso(entry.timestamp)]
617
+ ].map(([label, value]) => [label, typeof value === "string" ? singleLine(value) : ""]).filter(([, value]) => value !== "").map(([label, value]) => ({
618
+ type: "mrkdwn",
619
+ text: `*${label}:* ${truncateText(escapeSlack(value), SLACK_FIELD_MAX)}`
620
+ }));
621
+ if (context.length > 0) blocks.push({
622
+ type: "context",
623
+ elements: context
624
+ });
625
+ if (entry.data && Object.keys(entry.data).length > 0) {
626
+ const json = truncateText(escapeSlack(stringifyForLog(entry.data)), SLACK_DATA_MAX);
627
+ blocks.push({
628
+ type: "section",
629
+ text: {
630
+ type: "mrkdwn",
631
+ text: `\`\`\`${json}\`\`\``
632
+ }
633
+ });
634
+ }
635
+ return {
636
+ text: `${emoji} ${escapeSlack(truncateText(headline, SLACK_HEADER_MAX))}`,
637
+ blocks
638
+ };
639
+ }
640
+
641
+ //#endregion
642
+ export { DiscordTransport as n, SlackTransport as t };
643
+ //# sourceMappingURL=slack.transport-Bzw7sucI.mjs.map