logixia 1.13.3 → 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 (118) hide show
  1. package/README.md +72 -16
  2. package/dist/{azure-monitor.transport-BDFO4F-i.d.ts → azure-monitor.transport-Cn_3Usvu.d.ts} +2 -2
  3. package/dist/{azure-monitor.transport-BDFO4F-i.d.ts.map → azure-monitor.transport-Cn_3Usvu.d.ts.map} +1 -1
  4. package/dist/{azure-monitor.transport-D5wQEH04.d.mts → azure-monitor.transport-D7Da4RAb.d.mts} +2 -2
  5. package/dist/{azure-monitor.transport-D5wQEH04.d.mts.map → azure-monitor.transport-D7Da4RAb.d.mts.map} +1 -1
  6. package/dist/azure.d.mts +2 -2
  7. package/dist/azure.d.ts +2 -2
  8. package/dist/browser.d.mts +9 -0
  9. package/dist/browser.d.mts.map +1 -1
  10. package/dist/browser.d.ts +9 -0
  11. package/dist/browser.d.ts.map +1 -1
  12. package/dist/browser.js +43 -12
  13. package/dist/browser.js.map +1 -1
  14. package/dist/browser.mjs +43 -12
  15. package/dist/browser.mjs.map +1 -1
  16. package/dist/{nestjs-extras-ImbuDhs1.mjs → builder-0gEzRopq.mjs} +77 -871
  17. package/dist/builder-0gEzRopq.mjs.map +1 -0
  18. package/dist/{nestjs-extras-DnJ6vrIC.js → builder-D44QktIo.js} +81 -943
  19. package/dist/builder-D44QktIo.js.map +1 -0
  20. package/dist/cli/index.js +32 -13
  21. package/dist/cloudwatch.d.mts +2 -2
  22. package/dist/cloudwatch.d.ts +2 -2
  23. package/dist/{cloudwatch.transport-B00st6Xv.d.ts → cloudwatch.transport-DsEq633o.d.ts} +2 -2
  24. package/dist/{cloudwatch.transport-B00st6Xv.d.ts.map → cloudwatch.transport-DsEq633o.d.ts.map} +1 -1
  25. package/dist/{cloudwatch.transport-Blc7Gtxt.d.mts → cloudwatch.transport-UlyZFWMn.d.mts} +2 -2
  26. package/dist/{cloudwatch.transport-Blc7Gtxt.d.mts.map → cloudwatch.transport-UlyZFWMn.d.mts.map} +1 -1
  27. package/dist/gcp.d.mts +2 -2
  28. package/dist/gcp.d.ts +2 -2
  29. package/dist/{gcp.transport-m4fVw0b1.d.ts → gcp.transport-B7oc97sG.d.ts} +2 -2
  30. package/dist/{gcp.transport-m4fVw0b1.d.ts.map → gcp.transport-B7oc97sG.d.ts.map} +1 -1
  31. package/dist/{gcp.transport-zzNyIoih.d.mts → gcp.transport-GrJurKo8.d.mts} +2 -2
  32. package/dist/{gcp.transport-zzNyIoih.d.mts.map → gcp.transport-GrJurKo8.d.mts.map} +1 -1
  33. package/dist/{index-BPwEisy4.d.mts → index-3mw5gaCw.d.mts} +2 -2
  34. package/dist/{index-BPwEisy4.d.mts.map → index-3mw5gaCw.d.mts.map} +1 -1
  35. package/dist/{index-nZEeCr3X.d.ts → index-DhIoploR.d.ts} +2 -2
  36. package/dist/{index-nZEeCr3X.d.ts.map → index-DhIoploR.d.ts.map} +1 -1
  37. package/dist/index.d.mts +44 -47
  38. package/dist/index.d.mts.map +1 -1
  39. package/dist/index.d.ts +44 -47
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +38 -126
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +5 -58
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/logitron-logger-BFDSO8Io.d.mts +257 -0
  46. package/dist/logitron-logger-BFDSO8Io.d.mts.map +1 -0
  47. package/dist/logitron-logger-UVeN-mQm.d.ts +257 -0
  48. package/dist/logitron-logger-UVeN-mQm.d.ts.map +1 -0
  49. package/dist/middleware.d.mts +2 -2
  50. package/dist/middleware.d.ts +2 -2
  51. package/dist/nest.d.mts +481 -6
  52. package/dist/nest.d.mts.map +1 -1
  53. package/dist/nest.d.ts +481 -6
  54. package/dist/nest.d.ts.map +1 -1
  55. package/dist/nest.js +877 -15
  56. package/dist/nest.js.map +1 -0
  57. package/dist/nest.mjs +861 -6
  58. package/dist/nest.mjs.map +1 -0
  59. package/dist/{search-DjXSn6v5.mjs → search-BFbu_oVn.mjs} +13 -3
  60. package/dist/{search-DjXSn6v5.mjs.map → search-BFbu_oVn.mjs.map} +1 -1
  61. package/dist/{search-B0MH1SJJ.js → search-C4V0OMEn.js} +13 -3
  62. package/dist/{search-B0MH1SJJ.js.map → search-C4V0OMEn.js.map} +1 -1
  63. package/dist/{search-CHUZPl0h.d.ts → search-CNh9uVwR.d.ts} +2 -2
  64. package/dist/search-CNh9uVwR.d.ts.map +1 -0
  65. package/dist/{search-BfBzDIT9.d.mts → search-DXANsQ2n.d.mts} +2 -2
  66. package/dist/{search-CHUZPl0h.d.ts.map → search-DXANsQ2n.d.mts.map} +1 -1
  67. package/dist/search.d.mts +3 -3
  68. package/dist/search.d.ts +3 -3
  69. package/dist/search.js +1 -1
  70. package/dist/search.mjs +1 -1
  71. package/dist/slack.transport-BZzoXDPn.d.mts +112 -0
  72. package/dist/slack.transport-BZzoXDPn.d.mts.map +1 -0
  73. package/dist/slack.transport-BfJRouB2.js +654 -0
  74. package/dist/slack.transport-BfJRouB2.js.map +1 -0
  75. package/dist/slack.transport-Bzw7sucI.mjs +643 -0
  76. package/dist/slack.transport-Bzw7sucI.mjs.map +1 -0
  77. package/dist/slack.transport-CXjQYOes.d.ts +112 -0
  78. package/dist/slack.transport-CXjQYOes.d.ts.map +1 -0
  79. package/dist/testing.d.mts +2 -2
  80. package/dist/testing.d.ts +2 -2
  81. package/dist/{transport.manager-CpdVHEGP.mjs → transport.manager-BiuCAJd2.mjs} +171 -84
  82. package/dist/transport.manager-BiuCAJd2.mjs.map +1 -0
  83. package/dist/{transport.manager-Crau3Rle.js → transport.manager-CXbKKdVE.js} +176 -83
  84. package/dist/transport.manager-CXbKKdVE.js.map +1 -0
  85. package/dist/{transport.types-CtNr3D7S.d.mts → transport.types-BMnurPcc.d.mts} +5 -2
  86. package/dist/transport.types-BMnurPcc.d.mts.map +1 -0
  87. package/dist/{transport.types-DqVLZTyf.d.ts → transport.types-CGoHpWQR.d.ts} +5 -2
  88. package/dist/transport.types-CGoHpWQR.d.ts.map +1 -0
  89. package/dist/transports.d.mts +18 -13
  90. package/dist/transports.d.mts.map +1 -1
  91. package/dist/transports.d.ts +18 -13
  92. package/dist/transports.d.ts.map +1 -1
  93. package/dist/transports.js +5 -2
  94. package/dist/transports.mjs +4 -3
  95. package/dist/worker.d.mts +2 -2
  96. package/dist/worker.d.ts +2 -2
  97. package/dist/worker.js +1 -1
  98. package/dist/worker.mjs +1 -1
  99. package/dist/{worker.transport-JP8YHBuU.js → worker.transport-BdPxu3PN.js} +1 -1
  100. package/dist/{worker.transport-JP8YHBuU.js.map → worker.transport-BdPxu3PN.js.map} +1 -1
  101. package/dist/{worker.transport-Bj-i0P9V.d.ts → worker.transport-CdzSZJmw.d.ts} +2 -2
  102. package/dist/{worker.transport-Bj-i0P9V.d.ts.map → worker.transport-CdzSZJmw.d.ts.map} +1 -1
  103. package/dist/{worker.transport-Bgv9heDI.d.mts → worker.transport-JvGPog3C.d.mts} +2 -2
  104. package/dist/{worker.transport-Bgv9heDI.d.mts.map → worker.transport-JvGPog3C.d.mts.map} +1 -1
  105. package/dist/{worker.transport-otwhJ_Et.mjs → worker.transport-lPxO2MVL.mjs} +1 -1
  106. package/dist/{worker.transport-otwhJ_Et.mjs.map → worker.transport-lPxO2MVL.mjs.map} +1 -1
  107. package/package.json +18 -9
  108. package/dist/nestjs-extras-DnJ6vrIC.js.map +0 -1
  109. package/dist/nestjs-extras-ImbuDhs1.mjs.map +0 -1
  110. package/dist/nestjs-extras-OkC796py.d.mts +0 -725
  111. package/dist/nestjs-extras-OkC796py.d.mts.map +0 -1
  112. package/dist/nestjs-extras-mKrHE9S6.d.ts +0 -725
  113. package/dist/nestjs-extras-mKrHE9S6.d.ts.map +0 -1
  114. package/dist/search-BfBzDIT9.d.mts.map +0 -1
  115. package/dist/transport.manager-CpdVHEGP.mjs.map +0 -1
  116. package/dist/transport.manager-Crau3Rle.js.map +0 -1
  117. package/dist/transport.types-CtNr3D7S.d.mts.map +0 -1
  118. package/dist/transport.types-DqVLZTyf.d.ts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.transport-Bzw7sucI.mjs","names":["LEVEL_RANK: Record<string, number>","url: URL","opts: WebhookQueueOptions<T>","rendered: T[]","body: string","retryAfterMs: number | undefined","DISCORD_LEVEL_COLORS: Record<string, number>","out: DiscordEmbed","payload: Record<string, unknown>","fields: DiscordEmbedField[]","embed: DiscordEmbed","LEVEL_EMOJI: Record<string, string>","payload: Record<string, unknown>","blocks: unknown[]"],"sources":["../src/transports/webhook-queue.ts","../src/transports/discord.transport.ts","../src/transports/slack.transport.ts"],"sourcesContent":["/**\n * Shared core for the chat-webhook transports (Slack, Discord).\n *\n * - entries inside `minIntervalMs` are batched into one message; entries past\n * `maxPerMessage` are dropped and reported to the payload builder as overflow\n * - the queue is bounded (BufferCap); the oldest entries go first\n * - HTTP 429 waits for Retry-After (bounded, cancelled by close()) and retries a\n * bounded number of times\n * - an entry that cannot be rendered is dropped alone; an unserializable batch or a\n * missing global fetch is dropped, never retried\n * - errors only ever name the webhook host, never the URL (the URL is a secret)\n */\n\nimport type { TransportLogEntry } from '../types/transport.types';\nimport { internalError, internalWarn } from '../utils/internal-log';\nimport { HttpError, isPermanentHttpError } from './batch-buffer';\nimport { BufferCap } from './buffer-cap';\n\nconst MAX_BACKOFF_MS = 30_000;\nconst DEFAULT_QUEUE_CAP = 1000;\nconst CLOSE_TIMEOUT_MS = 3000;\nconst OVERFLOW_WARN_MS = 5000;\nexport const DEFAULT_WEBHOOK_TIMEOUT_MS = 10_000;\nconst ZWSP = '​';\n\nconst LEVEL_RANK: Record<string, number> = {\n fatal: 0,\n error: 1,\n warn: 2,\n warning: 2,\n info: 3,\n debug: 4,\n verbose: 5,\n trace: 6,\n};\n\nexport function resolveMinLevel(level: string | undefined, label: string): string {\n const resolved = (level ?? 'error').toLowerCase();\n if (!(resolved in LEVEL_RANK)) {\n throw new TypeError(`${label}: unknown level \"${String(level).slice(0, 40)}\"`);\n }\n return resolved;\n}\n\n/** Unknown (custom) entry levels pass, like the TransportManager does. */\nexport function meetsLevel(entryLevel: string, minLevel: string): boolean {\n const entryRank = LEVEL_RANK[entryLevel.toLowerCase()];\n return entryRank === undefined || entryRank <= (LEVEL_RANK[minLevel] ?? 1);\n}\n\nexport interface WebhookUrlRules {\n label: string;\n hostMatches: (host: string) => boolean;\n pathMatches: (path: string) => boolean;\n expected: string;\n allowCustomUrl: boolean | undefined;\n}\n\n/** Validates the secret URL. Error messages never contain it. */\nexport function parseWebhookUrl(raw: unknown, rules: WebhookUrlRules): URL {\n const { label, allowCustomUrl, hostMatches, pathMatches, expected } = rules;\n if (typeof raw !== 'string' || raw.length === 0) {\n throw new TypeError(`${label}: webhookUrl is required`);\n }\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new TypeError(`${label}: webhookUrl is not a valid URL`);\n }\n if (url.protocol !== 'https:') {\n throw new TypeError(`${label}: webhookUrl must use https:`);\n }\n if (url.username || url.password) {\n throw new TypeError(`${label}: webhookUrl must not contain credentials`);\n }\n if (!allowCustomUrl) {\n const host = url.hostname.toLowerCase();\n if (!hostMatches(host) || !pathMatches(url.pathname) || url.port) {\n throw new TypeError(\n `${label}: webhookUrl must be a ${expected} webhook (set allowCustomUrl: true to use another https endpoint)`\n );\n }\n }\n return url;\n}\n\nexport interface WebhookResponseLike {\n ok: boolean;\n status: number;\n headers?: { get(name: string): string | null } | undefined;\n body?: { cancel(): Promise<void> } | null | undefined;\n json?: () => Promise<unknown>;\n}\n\ninterface PostResult {\n ok: boolean;\n status: number;\n retryAfterMs: number | undefined;\n}\n\nexport interface WebhookQueueOptions<T> {\n label: string;\n url: URL;\n minIntervalMs: number | undefined;\n maxPerMessage: number;\n maxQueueSize: number | undefined;\n maxRetries: number | undefined;\n requestTimeoutMs: number | undefined;\n /** Renders one entry. A throw (or unserializable result) drops only that entry. */\n render: (entry: TransportLogEntry) => T;\n /**\n * Builds the JSON request body from the rendered entries. `overflow` counts\n * entries beyond `maxPerMessage` that were dropped; the body must say so.\n */\n assemble: (rendered: T[], overflow: number) => unknown;\n /** Reads the wait (ms) from a 429 response. Defaults to the Retry-After header. */\n retryAfterMs?: (res: WebhookResponseLike) => Promise<number | undefined>;\n}\n\nexport function retryAfterHeaderMs(res: WebhookResponseLike): number | undefined {\n const raw = res.headers?.get('retry-after');\n if (raw === null || raw === undefined || raw === '') return undefined;\n const seconds = Number(raw);\n return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1000 : undefined;\n}\n\nfunction nonNegative(value: number | undefined, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback;\n}\n\nfunction failureCode(err: unknown): string {\n const cause = (err as { cause?: { code?: unknown } } | null)?.cause;\n const code = cause?.code ?? (err as { code?: unknown } | null)?.code;\n if (typeof code === 'string') return code.slice(0, 40);\n return err instanceof Error ? err.name : 'error';\n}\n\nfunction sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise<void>((resolve) => {\n if (signal.aborted) return resolve();\n const done = (): void => {\n clearTimeout(timer);\n signal.removeEventListener('abort', done);\n resolve();\n };\n const timer = setTimeout(done, ms);\n timer.unref?.();\n signal.addEventListener('abort', done, { once: true });\n });\n}\n\nasync function cancelBody(res: WebhookResponseLike): Promise<void> {\n try {\n await res.body?.cancel();\n } catch {\n // body already consumed or locked\n }\n}\n\n/**\n * Throttled queue for chat webhooks. It is not built on BatchBuffer because the\n * semantics differ: sends are spaced by `minIntervalMs` (leading edge, then one\n * message per window), a window sends one summarized message and drops the\n * overflow, 429 waits are cancellable by close(), and flush() surfaces send\n * errors. HttpError / isPermanentHttpError are shared with BatchBuffer.\n *\n * Entries past `maxPerMessage` in one window are not sent: the message carries a\n * \"… and N more\" line and an internal warning reports the count.\n */\nexport class WebhookQueue<T> {\n private items: TransportLogEntry[] = [];\n private readonly cap: BufferCap;\n private readonly host: string;\n private readonly httpLabel: string;\n private readonly minIntervalMs: number;\n private readonly maxRetries: number;\n private readonly timeoutMs: number;\n private timer: NodeJS.Timeout | null = null;\n private inFlight: Promise<void> | undefined;\n private nextAllowed = 0;\n private failures = 0;\n private closed = false;\n private readonly shutdown = new AbortController();\n private overflowPending = 0;\n private lastOverflowWarn = 0;\n private fetchWarned = false;\n\n constructor(private readonly opts: WebhookQueueOptions<T>) {\n this.host = opts.url.hostname;\n this.httpLabel = `${opts.label} (${this.host})`;\n this.minIntervalMs = nonNegative(opts.minIntervalMs, 1000);\n this.maxRetries = Math.floor(nonNegative(opts.maxRetries, 1));\n this.timeoutMs = nonNegative(opts.requestTimeoutMs, DEFAULT_WEBHOOK_TIMEOUT_MS);\n this.cap = new BufferCap(opts.label, opts.maxQueueSize, opts.maxPerMessage, DEFAULT_QUEUE_CAP);\n }\n\n get length(): number {\n return this.items.length;\n }\n\n push(entry: TransportLogEntry): void {\n if (this.closed) return;\n this.items.push(entry);\n this.cap.enforce(this.items);\n this.schedule();\n }\n\n flush(): Promise<void> {\n this.inFlight ??= this.drain().finally(() => {\n this.inFlight = undefined;\n });\n return this.inFlight;\n }\n\n /** Cancels rate-limit waits, then makes one best-effort send with a short timeout. */\n async close(): Promise<void> {\n this.closed = true;\n this.clearTimer();\n this.shutdown.abort();\n try {\n await this.inFlight;\n } catch {\n // already reported by the timer path\n }\n try {\n await this.flush();\n } catch (err) {\n internalError(`${this.opts.label} flush on close failed`, err);\n }\n this.reportOverflow(true);\n if (this.items.length > 0) {\n internalError(`${this.opts.label} closing with ${this.items.length} undelivered entries`);\n this.items = [];\n }\n }\n\n private clearTimer(): void {\n if (this.timer) clearTimeout(this.timer);\n this.timer = null;\n }\n\n private schedule(): void {\n if (this.timer || this.closed || this.items.length === 0) return;\n const delay = Math.max(0, this.nextAllowed - Date.now());\n this.timer = setTimeout(() => {\n this.timer = null;\n this.flush().catch((err) => internalError(`${this.opts.label} send failed`, err));\n }, delay);\n this.timer.unref?.();\n }\n\n private reportOverflow(force = false): void {\n const now = Date.now();\n if (this.overflowPending === 0 || (!force && now - this.lastOverflowWarn < OVERFLOW_WARN_MS)) {\n return;\n }\n const n = this.overflowPending;\n internalWarn(\n `${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`\n );\n this.overflowPending = 0;\n this.lastOverflowWarn = now;\n }\n\n private render(batch: TransportLogEntry[]): T[] {\n const rendered: T[] = [];\n let bad = 0;\n for (const entry of batch) {\n try {\n const value = this.opts.render(entry);\n JSON.stringify(value);\n rendered.push(value);\n } catch (err) {\n bad += 1;\n if (bad === 1) {\n internalError(`${this.opts.label}: dropping an entry that cannot be rendered`, err);\n }\n }\n }\n if (bad > 1) {\n internalError(`${this.opts.label}: dropped ${bad} entries that cannot be rendered`);\n }\n return rendered;\n }\n\n private async drain(): Promise<void> {\n if (this.items.length === 0) return;\n if (typeof fetch !== 'function') {\n if (!this.fetchWarned) {\n this.fetchWarned = true;\n internalWarn(\n `${this.opts.label}: global fetch is unavailable (Node 18+ required); dropping entries`\n );\n }\n this.items = [];\n return;\n }\n const all = this.items;\n this.items = [];\n const batch = all.slice(0, this.opts.maxPerMessage);\n const overflow = all.length - batch.length;\n if (overflow > 0) {\n this.overflowPending += overflow;\n this.reportOverflow();\n }\n this.nextAllowed = Date.now() + this.minIntervalMs;\n try {\n const rendered = this.render(batch);\n if (rendered.length === 0 && overflow === 0) return;\n let body: string;\n try {\n body = JSON.stringify(this.opts.assemble(rendered, overflow));\n } catch (err) {\n internalError(`${this.opts.label}: dropping a batch that cannot be serialized`, err);\n return;\n }\n try {\n await this.sendWithRetry(body);\n this.failures = 0;\n } catch (err) {\n if (!isPermanentHttpError(err)) {\n this.items.unshift(...batch);\n this.cap.enforce(this.items);\n this.failures += 1;\n const backoff = Math.min(\n MAX_BACKOFF_MS,\n Math.max(1000, this.minIntervalMs) * 2 ** this.failures\n );\n this.nextAllowed = Math.max(this.nextAllowed, Date.now() + backoff);\n }\n throw err;\n }\n } finally {\n this.schedule();\n }\n }\n\n private async sendWithRetry(body: string): Promise<void> {\n for (let attempt = 0; ; attempt += 1) {\n const res = await this.post(body);\n if (res.ok) return;\n if (res.status !== 429) throw new HttpError(res.status, '', this.httpLabel);\n const waitMs = Math.min(\n MAX_BACKOFF_MS,\n Math.max(0, res.retryAfterMs ?? Math.max(1000, this.minIntervalMs))\n );\n this.nextAllowed = Math.max(this.nextAllowed, Date.now() + waitMs);\n if (attempt >= this.maxRetries || this.closed) {\n throw new HttpError(429, '', this.httpLabel);\n }\n await sleep(waitMs, this.shutdown.signal);\n if (this.shutdown.signal.aborted) {\n throw new Error(`${this.opts.label}: rate-limit wait cancelled by close()`);\n }\n }\n }\n\n private async readRetryAfter(res: WebhookResponseLike): Promise<number | undefined> {\n try {\n return this.opts.retryAfterMs ? await this.opts.retryAfterMs(res) : retryAfterHeaderMs(res);\n } catch {\n return undefined;\n }\n }\n\n private async post(body: string): Promise<PostResult> {\n const controller = new AbortController();\n const timeoutMs = this.closed ? Math.min(this.timeoutMs, CLOSE_TIMEOUT_MS) : this.timeoutMs;\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = (await fetch(this.opts.url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n redirect: 'error',\n signal: controller.signal,\n })) as WebhookResponseLike;\n let retryAfterMs: number | undefined;\n try {\n if (res.status === 429) retryAfterMs = await this.readRetryAfter(res);\n } finally {\n await cancelBody(res);\n }\n return { ok: res.ok, status: res.status, retryAfterMs };\n } catch (err) {\n // fetch errors may echo the request URL; keep only the host and a short code (no cause)\n // eslint-disable-next-line preserve-caught-error -- the cause can contain the secret URL\n throw new Error(\n `${this.opts.label}: request to ${this.host} failed (${controller.signal.aborted ? 'timeout' : failureCode(err)})`\n );\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\n/** Cuts `text` to `max` chars without splitting a surrogate pair or a `&amp;`-style entity. */\nexport function truncateText(text: string, max: number, ellipsis = '…'): string {\n if (text.length <= max) return text;\n let cut = text.slice(0, Math.max(0, max - ellipsis.length));\n cut = cut.replace(/&[a-z]{0,3}$/, '');\n if (/[\\uD800-\\uDBFF]$/.test(cut)) cut = cut.slice(0, -1);\n return cut + ellipsis;\n}\n\n/** Breaks code-fence and broadcast-mention sequences with a zero-width space. */\nexport function defuseText(text: string): string {\n return text\n .replace(/`{3,}/g, (m) => m.split('').join(ZWSP))\n .replace(/@(everyone|here|channel)/gi, `@${ZWSP}$1`);\n}\n\n/** Collapses control characters and newlines for single-line fields. */\nexport function singleLine(text: string): string {\n // eslint-disable-next-line no-control-regex\n return text.replace(/[\\u0000-\\u001f\\u007f]+/g, ' ').trim();\n}\n\n/** ISO string, or undefined for a missing or invalid timestamp. */\nexport function safeIso(date: unknown): string | undefined {\n return date instanceof Date && !Number.isNaN(date.getTime()) ? date.toISOString() : undefined;\n}\n","/**\n * Discord webhook transport.\n *\n * Sends error-level (by default) entries to a Discord channel as embeds.\n * Entries inside `minIntervalMs` are batched into one request (max 10 embeds;\n * the overflow is dropped and counted in the message `content`). Every request\n * carries `allowed_mentions: { parse: [] }` so logged data can never ping\n * @everyone, roles or users, and logged text has its markdown escaped.\n *\n * Limits follow https://discord.com/developers/docs/resources/message#embed-object-embed-limits\n *\n * The webhook URL is a secret: it is never logged and never appears in errors.\n *\n * @example\n * transports: { custom: [ new DiscordTransport({ webhookUrl: process.env.DISCORD_WEBHOOK_URL! }) ] }\n */\n\nimport type { IAsyncTransport, TransportLogEntry } from '../types/transport.types';\nimport { internalWarn } from '../utils/internal-log';\nimport { stringifyForLog } from '../utils/safe-stringify';\nimport {\n DEFAULT_WEBHOOK_TIMEOUT_MS,\n defuseText,\n meetsLevel,\n parseWebhookUrl,\n resolveMinLevel,\n retryAfterHeaderMs,\n safeIso,\n singleLine,\n truncateText,\n WebhookQueue,\n type WebhookResponseLike,\n} from './webhook-queue';\n\nexport const DISCORD_TITLE_MAX = 256;\nexport const DISCORD_DESCRIPTION_MAX = 4096;\nexport const DISCORD_FIELD_VALUE_MAX = 1024;\nexport const DISCORD_EMBEDS_MAX = 10;\nexport const DISCORD_FIELDS_MAX = 25;\nexport const DISCORD_EMBED_TOTAL_MAX = 6000;\nexport const DISCORD_CONTENT_MAX = 2000;\nconst FIELD_VALUE_CAP = 64;\nconst DISCORD_FOOTER_MAX = 2048;\nconst FENCE_OVERHEAD = '```json\\n\\n```'.length;\nconst MIN_USEFUL_BODY = 20;\n\nexport interface DiscordEmbedField {\n name: string;\n value: string;\n inline?: boolean;\n}\n\nexport interface DiscordEmbed {\n title?: string;\n description?: string;\n color?: number;\n timestamp?: string;\n fields?: DiscordEmbedField[];\n [key: string]: unknown;\n}\n\nexport interface DiscordTransportConfig {\n /** Webhook URL (secret). Must be https on discord.com / discordapp.com unless `allowCustomUrl`. */\n webhookUrl: string;\n /** Minimum level to send. Default: 'error'. */\n level?: string;\n username?: string;\n /** https URL of the avatar shown for the webhook. */\n avatarUrl?: string;\n /** Entries within this window are batched into one request. Default: 1000. */\n minIntervalMs?: number;\n /**\n * Overrides the embed built for each entry. Limits are still enforced, but\n * escaping the content is the formatter's responsibility.\n */\n formatter?: (entry: TransportLogEntry) => DiscordEmbed;\n filter?: (entry: TransportLogEntry) => boolean;\n /** Accept an https webhook URL on any host (e.g. a proxy or Discord-compatible service). */\n allowCustomUrl?: boolean;\n /** Retries after an HTTP 429 (waits retry_after, max 30s). Default: 1. */\n maxRetries?: number;\n /** Max queued entries; the oldest are dropped past this. Default: 1000. */\n maxBufferSize?: number;\n requestTimeoutMs?: number;\n}\n\nexport const DISCORD_LEVEL_COLORS: Record<string, number> = {\n fatal: 0x992d22,\n error: 0xed4245,\n warn: 0xfee75c,\n warning: 0xfee75c,\n info: 0x3498db,\n debug: 0x95a5a6,\n verbose: 0x95a5a6,\n trace: 0x95a5a6,\n};\nconst DEFAULT_COLOR = 0x95a5a6;\n\n/** Escapes Discord markdown and defuses @everyone / @here and code fences. */\nexport function escapeDiscord(text: string): string {\n return defuseText(text.replace(/[\\\\*_~`|>#[\\]()]/g, '\\\\$&'));\n}\n\nfunction textLength(value: unknown, key: string): number {\n const text = (value as Record<string, unknown> | undefined)?.[key];\n return typeof text === 'string' ? text.length : 0;\n}\n\n/** Counts every text field Discord counts toward the 6000-character limit. */\nfunction embedChars(embed: DiscordEmbed): number {\n return (\n (embed.title?.length ?? 0) +\n (embed.description?.length ?? 0) +\n (embed.fields ?? []).reduce((n, f) => n + f.name.length + f.value.length, 0) +\n textLength(embed['footer'], 'text') +\n textLength(embed['author'], 'name')\n );\n}\n\n/** Shrinks one embed to `limit` chars: description first, then fields, footer/author, title. */\nfunction shrinkToFit(embed: DiscordEmbed, limit: number): void {\n const excess = () => embedChars(embed) - limit;\n if (excess() > 0 && embed.description) {\n const keep = embed.description.length - excess();\n if (keep > 0) embed.description = truncateText(embed.description, keep);\n else delete embed.description;\n }\n while (excess() > 0 && embed.fields?.length) embed.fields.pop();\n if (excess() > 0) {\n delete embed['footer'];\n delete embed['author'];\n }\n if (excess() > 0 && embed.title) {\n embed.title = truncateText(embed.title, Math.max(1, embed.title.length - excess()));\n }\n}\n\nfunction codeBlock(json: string, max: number): string {\n return `\\`\\`\\`json\\n${truncateText(\n json.replace(/`{3,}/g, (m) => m.split('').join('​')),\n max\n )}\\n\\`\\`\\``;\n}\n\nfunction clampCustomEmbed(embed: DiscordEmbed): DiscordEmbed {\n const out: DiscordEmbed = { ...embed };\n if (typeof out.title === 'string') out.title = truncateText(out.title, DISCORD_TITLE_MAX);\n if (typeof out.description === 'string') {\n out.description = truncateText(out.description, DISCORD_DESCRIPTION_MAX);\n }\n if (Array.isArray(out.fields)) {\n out.fields = out.fields.slice(0, DISCORD_FIELDS_MAX).map((f) => ({\n ...f,\n name: truncateText(String(f.name), DISCORD_TITLE_MAX),\n value: truncateText(String(f.value), DISCORD_FIELD_VALUE_MAX),\n }));\n }\n const footer = out['footer'] as { text?: unknown } | undefined;\n if (footer && typeof footer.text === 'string') {\n out['footer'] = { ...footer, text: truncateText(footer.text, DISCORD_FOOTER_MAX) };\n }\n const author = out['author'] as { name?: unknown } | undefined;\n if (author && typeof author.name === 'string') {\n out['author'] = { ...author, name: truncateText(author.name, DISCORD_TITLE_MAX) };\n }\n return out;\n}\n\ninterface EmbedDraft {\n embed: DiscordEmbed;\n json: string | undefined;\n}\n\nexport class DiscordTransport implements IAsyncTransport {\n public readonly name = 'discord';\n public readonly level: string;\n public filter?: (entry: TransportLogEntry) => boolean;\n\n private readonly queue!: WebhookQueue<EmbedDraft>;\n private readonly username: string | undefined;\n private readonly avatarUrl: string | undefined;\n private readonly formatter: ((entry: TransportLogEntry) => DiscordEmbed) | undefined;\n\n constructor(config: DiscordTransportConfig) {\n const url = parseWebhookUrl(config.webhookUrl, {\n label: 'DiscordTransport',\n hostMatches: (h) =>\n ['discord.com', 'discordapp.com', 'ptb.discord.com', 'canary.discord.com'].includes(h),\n pathMatches: (p) => /^\\/api(\\/v\\d+)?\\/webhooks\\//.test(p),\n expected: 'discord.com',\n allowCustomUrl: config.allowCustomUrl,\n });\n if (config.avatarUrl !== undefined && !/^https:\\/\\//i.test(config.avatarUrl)) {\n throw new TypeError('DiscordTransport: avatarUrl must use https:');\n }\n this.level = resolveMinLevel(config.level, 'DiscordTransport');\n if (config.filter) this.filter = config.filter;\n this.username = config.username;\n this.avatarUrl = config.avatarUrl;\n this.formatter = config.formatter;\n const queue = new WebhookQueue<EmbedDraft>({\n label: 'DiscordTransport',\n url,\n minIntervalMs: config.minIntervalMs,\n maxPerMessage: DISCORD_EMBEDS_MAX,\n maxQueueSize: config.maxBufferSize,\n maxRetries: config.maxRetries,\n requestTimeoutMs: config.requestTimeoutMs ?? DEFAULT_WEBHOOK_TIMEOUT_MS,\n render: (entry) => this.draftFor(entry),\n assemble: (drafts, overflow) => this.buildPayload(drafts, overflow),\n retryAfterMs: readRetryAfter,\n });\n // non-enumerable so util.inspect / spreads of the transport never reach the secret URL\n Object.defineProperty(this, 'queue', { value: queue, enumerable: false });\n }\n\n write(entry: TransportLogEntry): void {\n if (!meetsLevel(entry.level, this.level)) return;\n // `filter` is applied by the TransportManager; it is not run again here\n this.queue.push(entry);\n }\n\n flush(): Promise<void> {\n return this.queue.flush();\n }\n\n close(): Promise<void> {\n return this.queue.close();\n }\n\n /** Never serialise the webhook URL (JSON.stringify, util.inspect). */\n toJSON(): Record<string, unknown> {\n return { name: this.name, level: this.level };\n }\n\n private buildPayload(drafts: EmbedDraft[], overflow: number): Record<string, unknown> {\n const kept = this.fitToBudget(drafts);\n const dropped = overflow + (drafts.length - kept.length);\n\n const payload: Record<string, unknown> = {\n embeds: kept.map((d) => d.embed),\n allowed_mentions: { parse: [] },\n };\n if (dropped > 0) {\n payload['content'] = truncateText(\n `… and ${dropped} more entr${dropped === 1 ? 'y' : 'ies'} not shown`,\n DISCORD_CONTENT_MAX\n );\n }\n if (this.username) payload['username'] = truncateText(this.username, 80);\n if (this.avatarUrl) payload['avatar_url'] = this.avatarUrl;\n return payload;\n }\n\n /** Enforces the 6000-char total: drops trailing embeds if needed, then shares the rest among descriptions. */\n private fitToBudget(drafts: EmbedDraft[]): EmbedDraft[] {\n const kept = [...drafts];\n const base = () => kept.reduce((n, d) => n + embedChars(d.embed), 0);\n while (kept.length > 1 && base() > DISCORD_EMBED_TOTAL_MAX) kept.pop();\n const only = kept[0];\n if (only && base() > DISCORD_EMBED_TOTAL_MAX) shrinkToFit(only.embed, DISCORD_EMBED_TOTAL_MAX);\n const withData = kept.filter((d) => d.json !== undefined);\n if (withData.length === 0) return kept;\n const share = Math.floor((DISCORD_EMBED_TOTAL_MAX - base()) / withData.length);\n const body = Math.min(DISCORD_DESCRIPTION_MAX, share) - FENCE_OVERHEAD;\n for (const d of withData) {\n if (body >= MIN_USEFUL_BODY) d.embed.description = codeBlock(d.json!, body);\n }\n return kept;\n }\n\n private draftFor(entry: TransportLogEntry): EmbedDraft {\n if (this.formatter) {\n try {\n return { embed: clampCustomEmbed(this.formatter(entry)), json: undefined };\n } catch {\n internalWarn('DiscordTransport: formatter threw; using the default embed');\n }\n }\n return defaultDraft(entry);\n }\n}\n\nfunction defaultDraft(entry: TransportLogEntry): EmbedDraft {\n const title = `[${singleLine(entry.level).toUpperCase()}] ${escapeDiscord(singleLine(entry.message))}`;\n const candidates: Array<[string, string | undefined]> = [\n ['appName', entry.appName],\n ['environment', entry.environment],\n ['context', entry.context],\n ['traceId', entry.traceId],\n ];\n const fields: DiscordEmbedField[] = candidates\n .map(([name, value]) => [name, typeof value === 'string' ? singleLine(value) : ''] as const)\n .filter(([, value]) => value !== '')\n .map(([name, value]) => ({\n name,\n value: truncateText(escapeDiscord(value), FIELD_VALUE_CAP),\n inline: true,\n }));\n const embed: DiscordEmbed = {\n title: truncateText(title, DISCORD_TITLE_MAX),\n color: DISCORD_LEVEL_COLORS[entry.level.toLowerCase()] ?? DEFAULT_COLOR,\n fields,\n };\n const timestamp = safeIso(entry.timestamp);\n if (timestamp) embed.timestamp = timestamp;\n const json =\n entry.data && Object.keys(entry.data).length > 0 ? stringifyForLog(entry.data) : undefined;\n return { embed, json };\n}\n\nasync function readRetryAfter(res: WebhookResponseLike): Promise<number | undefined> {\n try {\n const body = (await res.json?.()) as { retry_after?: unknown } | undefined;\n const seconds = Number(body?.retry_after);\n if (body?.retry_after !== undefined && Number.isFinite(seconds) && seconds >= 0) {\n return seconds * 1000;\n }\n } catch {\n // fall through to the header\n }\n return retryAfterHeaderMs(res);\n}\n","/**\n * Slack incoming-webhook transport.\n *\n * Sends error-level (by default) entries to a Slack channel as Block Kit\n * messages. Entries inside `minIntervalMs` are batched into one message so a\n * log storm cannot flood the channel. All logged text is escaped for Slack\n * mrkdwn (`&`, `<`, `>`), which also disarms `<!channel>`, `<!here>` and\n * `<@U123>` mentions coming from logged data.\n *\n * The webhook URL is a secret: it is never logged and never appears in errors.\n *\n * @example\n * transports: { custom: [ new SlackTransport({ webhookUrl: process.env.SLACK_WEBHOOK_URL! }) ] }\n */\n\nimport type { IAsyncTransport, TransportLogEntry } from '../types/transport.types';\nimport { internalWarn } from '../utils/internal-log';\nimport { stringifyForLog } from '../utils/safe-stringify';\nimport {\n DEFAULT_WEBHOOK_TIMEOUT_MS,\n defuseText,\n meetsLevel,\n parseWebhookUrl,\n resolveMinLevel,\n safeIso,\n singleLine,\n truncateText,\n WebhookQueue,\n} from './webhook-queue';\n\nexport const SLACK_HEADER_MAX = 150;\nexport const SLACK_DATA_MAX = 2500;\nconst SLACK_FIELD_MAX = 300;\nconst SLACK_TEXT_MAX = 1000;\nconst SLACK_MAX_BLOCKS = 50;\nconst SLACK_MAX_PER_MESSAGE = 10;\n\n/** A Slack message body as returned by a custom `formatter`. */\nexport interface SlackMessage {\n text?: string;\n blocks?: unknown[];\n [key: string]: unknown;\n}\n\nexport interface SlackTransportConfig {\n /** Incoming-webhook URL (secret). Must be https on hooks.slack.com unless `allowCustomUrl`. */\n webhookUrl: string;\n /** Minimum level to send. Default: 'error'. */\n level?: string;\n username?: string;\n iconEmoji?: string;\n /** Entries within this window are batched into one message. Default: 1000. */\n minIntervalMs?: number;\n /**\n * Overrides the message built for each entry. The results of one window are\n * merged: texts are joined and blocks concatenated (capped at 50 blocks).\n * Escaping the content is the formatter's responsibility.\n */\n formatter?: (entry: TransportLogEntry) => SlackMessage;\n filter?: (entry: TransportLogEntry) => boolean;\n /** Accept an https webhook URL on any host (e.g. a proxy or Slack-compatible service). */\n allowCustomUrl?: boolean;\n /** Retries after an HTTP 429 (waits Retry-After, max 30s). Default: 1. */\n maxRetries?: number;\n /** Max queued entries; the oldest are dropped past this. Default: 1000. */\n maxBufferSize?: number;\n requestTimeoutMs?: number;\n}\n\n/** Escapes the three characters Slack treats specially and defuses broadcast mentions. */\nexport function escapeSlack(text: string): string {\n return defuseText(text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'));\n}\n\nconst LEVEL_EMOJI: Record<string, string> = {\n fatal: ':rotating_light:',\n error: ':red_circle:',\n warn: ':warning:',\n warning: ':warning:',\n info: ':information_source:',\n};\n\nexport class SlackTransport implements IAsyncTransport {\n public readonly name = 'slack';\n public readonly level: string;\n public filter?: (entry: TransportLogEntry) => boolean;\n\n private readonly queue!: WebhookQueue<SlackMessage>;\n private readonly username: string | undefined;\n private readonly iconEmoji: string | undefined;\n private readonly formatter: ((entry: TransportLogEntry) => SlackMessage) | undefined;\n\n constructor(config: SlackTransportConfig) {\n const url = parseWebhookUrl(config.webhookUrl, {\n label: 'SlackTransport',\n hostMatches: (h) => h === 'hooks.slack.com' || h === 'hooks.slack-gov.com',\n pathMatches: (p) => p.startsWith('/services/') || p.startsWith('/triggers/'),\n expected: 'hooks.slack.com',\n allowCustomUrl: config.allowCustomUrl,\n });\n this.level = resolveMinLevel(config.level, 'SlackTransport');\n if (config.filter) this.filter = config.filter;\n this.username = config.username;\n this.iconEmoji = config.iconEmoji;\n this.formatter = config.formatter;\n const queue = new WebhookQueue<SlackMessage>({\n label: 'SlackTransport',\n url,\n minIntervalMs: config.minIntervalMs,\n maxPerMessage: SLACK_MAX_PER_MESSAGE,\n maxQueueSize: config.maxBufferSize,\n maxRetries: config.maxRetries,\n requestTimeoutMs: config.requestTimeoutMs ?? DEFAULT_WEBHOOK_TIMEOUT_MS,\n render: (entry) => this.messageFor(entry),\n assemble: (messages, overflow) => this.buildPayload(messages, overflow),\n });\n // non-enumerable so util.inspect / spreads of the transport never reach the secret URL\n Object.defineProperty(this, 'queue', { value: queue, enumerable: false });\n }\n\n write(entry: TransportLogEntry): void {\n if (!meetsLevel(entry.level, this.level)) return;\n // `filter` is applied by the TransportManager; it is not run again here\n this.queue.push(entry);\n }\n\n flush(): Promise<void> {\n return this.queue.flush();\n }\n\n close(): Promise<void> {\n return this.queue.close();\n }\n\n /** Never serialise the webhook URL (JSON.stringify, util.inspect). */\n toJSON(): Record<string, unknown> {\n return { name: this.name, level: this.level };\n }\n\n private buildPayload(messages: SlackMessage[], overflow: number): Record<string, unknown> {\n const blocks = messages.flatMap((m) => m.blocks ?? []);\n const plural = overflow === 1 ? 'y' : 'ies';\n const counter = overflow > 0 ? `… and ${overflow} more entr${plural} not shown` : '';\n if (counter) {\n blocks.push({ type: 'context', elements: [{ type: 'mrkdwn', text: counter }] });\n }\n const text = [...messages.map((m) => m.text ?? ''), counter].filter(Boolean).join('\\n');\n const payload: Record<string, unknown> = {\n text: truncateText(text, SLACK_TEXT_MAX),\n blocks: blocks.slice(0, SLACK_MAX_BLOCKS),\n };\n if (this.username) payload['username'] = truncateText(this.username, 80);\n if (this.iconEmoji) payload['icon_emoji'] = this.iconEmoji;\n return payload;\n }\n\n private messageFor(entry: TransportLogEntry): SlackMessage {\n if (this.formatter) {\n try {\n return this.formatter(entry);\n } catch {\n internalWarn('SlackTransport: formatter threw; using the default message');\n }\n }\n return defaultMessage(entry);\n }\n}\n\nfunction defaultMessage(entry: TransportLogEntry): SlackMessage {\n const level = singleLine(entry.level).toUpperCase();\n const emoji = LEVEL_EMOJI[entry.level.toLowerCase()] ?? ':white_circle:';\n const headline = `[${level}] ${singleLine(entry.message)}`;\n const blocks: unknown[] = [\n {\n type: 'header',\n text: {\n type: 'plain_text',\n text: truncateText(escapeSlack(headline), SLACK_HEADER_MAX),\n emoji: false,\n },\n },\n ];\n\n const context = [\n ['app', entry.appName],\n ['env', entry.environment],\n ['context', entry.context],\n ['trace', entry.traceId],\n ['time', safeIso(entry.timestamp)],\n ]\n .map(([label, value]) => [label, typeof value === 'string' ? singleLine(value) : ''] as const)\n .filter(([, value]) => value !== '')\n .map(([label, value]) => ({\n type: 'mrkdwn',\n text: `*${label}:* ${truncateText(escapeSlack(value), SLACK_FIELD_MAX)}`,\n }));\n if (context.length > 0) blocks.push({ type: 'context', elements: context });\n\n if (entry.data && Object.keys(entry.data).length > 0) {\n const json = truncateText(escapeSlack(stringifyForLog(entry.data)), SLACK_DATA_MAX);\n blocks.push({ type: 'section', text: { type: 'mrkdwn', text: `\\`\\`\\`${json}\\`\\`\\`` } });\n }\n\n return {\n text: `${emoji} ${escapeSlack(truncateText(headline, SLACK_HEADER_MAX))}`,\n blocks,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ import { p as TransportLogEntry, s as IAsyncTransport } from "./transport.types-CGoHpWQR.js";
2
+
3
+ //#region src/transports/discord.transport.d.ts
4
+
5
+ interface DiscordEmbedField {
6
+ name: string;
7
+ value: string;
8
+ inline?: boolean;
9
+ }
10
+ interface DiscordEmbed {
11
+ title?: string;
12
+ description?: string;
13
+ color?: number;
14
+ timestamp?: string;
15
+ fields?: DiscordEmbedField[];
16
+ [key: string]: unknown;
17
+ }
18
+ interface DiscordTransportConfig {
19
+ /** Webhook URL (secret). Must be https on discord.com / discordapp.com unless `allowCustomUrl`. */
20
+ webhookUrl: string;
21
+ /** Minimum level to send. Default: 'error'. */
22
+ level?: string;
23
+ username?: string;
24
+ /** https URL of the avatar shown for the webhook. */
25
+ avatarUrl?: string;
26
+ /** Entries within this window are batched into one request. Default: 1000. */
27
+ minIntervalMs?: number;
28
+ /**
29
+ * Overrides the embed built for each entry. Limits are still enforced, but
30
+ * escaping the content is the formatter's responsibility.
31
+ */
32
+ formatter?: (entry: TransportLogEntry) => DiscordEmbed;
33
+ filter?: (entry: TransportLogEntry) => boolean;
34
+ /** Accept an https webhook URL on any host (e.g. a proxy or Discord-compatible service). */
35
+ allowCustomUrl?: boolean;
36
+ /** Retries after an HTTP 429 (waits retry_after, max 30s). Default: 1. */
37
+ maxRetries?: number;
38
+ /** Max queued entries; the oldest are dropped past this. Default: 1000. */
39
+ maxBufferSize?: number;
40
+ requestTimeoutMs?: number;
41
+ }
42
+ declare class DiscordTransport implements IAsyncTransport {
43
+ readonly name = "discord";
44
+ readonly level: string;
45
+ filter?: (entry: TransportLogEntry) => boolean;
46
+ private readonly queue;
47
+ private readonly username;
48
+ private readonly avatarUrl;
49
+ private readonly formatter;
50
+ constructor(config: DiscordTransportConfig);
51
+ write(entry: TransportLogEntry): void;
52
+ flush(): Promise<void>;
53
+ close(): Promise<void>;
54
+ /** Never serialise the webhook URL (JSON.stringify, util.inspect). */
55
+ toJSON(): Record<string, unknown>;
56
+ private buildPayload;
57
+ /** Enforces the 6000-char total: drops trailing embeds if needed, then shares the rest among descriptions. */
58
+ private fitToBudget;
59
+ private draftFor;
60
+ }
61
+ //#endregion
62
+ //#region src/transports/slack.transport.d.ts
63
+ /** A Slack message body as returned by a custom `formatter`. */
64
+ interface SlackMessage {
65
+ text?: string;
66
+ blocks?: unknown[];
67
+ [key: string]: unknown;
68
+ }
69
+ interface SlackTransportConfig {
70
+ /** Incoming-webhook URL (secret). Must be https on hooks.slack.com unless `allowCustomUrl`. */
71
+ webhookUrl: string;
72
+ /** Minimum level to send. Default: 'error'. */
73
+ level?: string;
74
+ username?: string;
75
+ iconEmoji?: string;
76
+ /** Entries within this window are batched into one message. Default: 1000. */
77
+ minIntervalMs?: number;
78
+ /**
79
+ * Overrides the message built for each entry. The results of one window are
80
+ * merged: texts are joined and blocks concatenated (capped at 50 blocks).
81
+ * Escaping the content is the formatter's responsibility.
82
+ */
83
+ formatter?: (entry: TransportLogEntry) => SlackMessage;
84
+ filter?: (entry: TransportLogEntry) => boolean;
85
+ /** Accept an https webhook URL on any host (e.g. a proxy or Slack-compatible service). */
86
+ allowCustomUrl?: boolean;
87
+ /** Retries after an HTTP 429 (waits Retry-After, max 30s). Default: 1. */
88
+ maxRetries?: number;
89
+ /** Max queued entries; the oldest are dropped past this. Default: 1000. */
90
+ maxBufferSize?: number;
91
+ requestTimeoutMs?: number;
92
+ }
93
+ declare class SlackTransport implements IAsyncTransport {
94
+ readonly name = "slack";
95
+ readonly level: string;
96
+ filter?: (entry: TransportLogEntry) => boolean;
97
+ private readonly queue;
98
+ private readonly username;
99
+ private readonly iconEmoji;
100
+ private readonly formatter;
101
+ constructor(config: SlackTransportConfig);
102
+ write(entry: TransportLogEntry): void;
103
+ flush(): Promise<void>;
104
+ close(): Promise<void>;
105
+ /** Never serialise the webhook URL (JSON.stringify, util.inspect). */
106
+ toJSON(): Record<string, unknown>;
107
+ private buildPayload;
108
+ private messageFor;
109
+ }
110
+ //#endregion
111
+ export { DiscordTransport as a, DiscordEmbed as i, SlackTransport as n, DiscordTransportConfig as o, SlackTransportConfig as r, SlackMessage as t };
112
+ //# sourceMappingURL=slack.transport-CXjQYOes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.transport-CXjQYOes.d.ts","names":[],"sources":["../src/transports/discord.transport.ts","../src/transports/slack.transport.ts"],"sourcesContent":[],"mappings":";;;;AC0DsB,UDZL,iBAAA,CCYK;EAAsB,IAAA,EAAA,MAAA;EACzB,KAAA,EAAA,MAAA;EAAiB,MAAA,CAAA,EAAA,OAAA;AAuBpC;AAG0B,UDjCT,YAAA,CCiCS;EAOJ,KAAA,CAAA,EAAA,MAAA;EA4BP,WAAA,CAAA,EAAA,MAAA;EAMJ,KAAA,CAAA,EAAA,MAAA;EAIA,SAAA,CAAA,EAAA,MAAA;EAKC,MAAA,CAAA,ED9ED,iBC8EC,EAAA;EArD2B,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,OAAA;;UDrBtB,sBAAA;;;;;;;;;;;;;;sBAcK,sBAAsB;mBACzB;;;;;;;;;cAiGN,gBAAA,YAA4B;;;mBAGf;;;;;sBAOJ;eAiCP;WAMJ;WAIA;;YAKC;;;;;;;;;AA1D4C,UCvIvC,YAAA,CDuIuC;;;;ACvIxD;AAMiB,UAAA,oBAAA,CAAoB;EAcf;EAAsB,UAAA,EAAA,MAAA;EACzB;EAAiB,KAAA,CAAA,EAAA,MAAA;EAuBvB,QAAA,CAAA,EAAA,MAAe;EAGF,SAAA,CAAA,EAAA,MAAA;EAOJ;EA4BP,aAAA,CAAA,EAAA,MAAA;EAMJ;;;;;sBApEW,sBAAsB;mBACzB;;;;;;;;;cAuBN,cAAA,YAA0B;;;mBAGb;;;;;sBAOJ;eA4BP;WAMJ;WAIA;;YAKC"}
@@ -1,5 +1,5 @@
1
- import "./transport.types-CtNr3D7S.mjs";
2
- import { h as LogEntry, u as IBaseLogger } from "./index-BPwEisy4.mjs";
1
+ import "./transport.types-BMnurPcc.mjs";
2
+ import { h as LogEntry, u as IBaseLogger } from "./index-3mw5gaCw.mjs";
3
3
 
4
4
  //#region src/testing/mock-logger.d.ts
5
5
 
package/dist/testing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import "./transport.types-DqVLZTyf.js";
2
- import { h as LogEntry, u as IBaseLogger } from "./index-nZEeCr3X.js";
1
+ import "./transport.types-CGoHpWQR.js";
2
+ import { h as LogEntry, u as IBaseLogger } from "./index-DhIoploR.js";
3
3
 
4
4
  //#region src/testing/mock-logger.d.ts
5
5
 
@@ -6,9 +6,9 @@ import { t as safeToString } from "./coerce.utils-Cu6IUTgX.mjs";
6
6
  import * as fs from "node:fs";
7
7
  import * as path$1 from "node:path";
8
8
  import { EventEmitter } from "node:events";
9
- import { pipeline } from "node:stream/promises";
10
9
  import { promisify } from "node:util";
11
- import { createGzip } from "node:zlib";
10
+ import { createGzip, gzip } from "node:zlib";
11
+ import { pipeline } from "node:stream/promises";
12
12
 
13
13
  //#region src/utils/sanitize.utils.ts
14
14
  /**
@@ -766,6 +766,17 @@ var DatabaseTransport = class {
766
766
 
767
767
  //#endregion
768
768
  //#region src/transports/analytics.transport.ts
769
+ /**
770
+ * Thrown by sendBatch() when part of a batch was already delivered. drain()
771
+ * requeues only `unsent`, so delivered entries are not sent twice.
772
+ */
773
+ var PartialBatchError = class extends Error {
774
+ constructor(message, unsent, options) {
775
+ super(message, options);
776
+ this.unsent = unsent;
777
+ this.name = "PartialBatchError";
778
+ }
779
+ };
769
780
  var AnalyticsTransport = class {
770
781
  constructor(name, config) {
771
782
  this.batch = [];
@@ -826,7 +837,7 @@ var AnalyticsTransport = class {
826
837
  await this.sendBatch(entriesToSend);
827
838
  } catch (error) {
828
839
  internalError(`Analytics transport ${this.name} flush failed`, error);
829
- this.batch.unshift(...entriesToSend);
840
+ this.batch.unshift(...error instanceof PartialBatchError ? error.unsent : entriesToSend);
830
841
  this.cap.enforce(this.batch);
831
842
  return;
832
843
  }
@@ -889,8 +900,71 @@ var AnalyticsTransport = class {
889
900
 
890
901
  //#endregion
891
902
  //#region src/transports/datadog.transport.ts
903
+ const gzipAsync = promisify(gzip);
904
+ const MAX_LOGS_PER_REQUEST = 1e3;
905
+ const MAX_LOGS_PAYLOAD_BYTES = 5e6;
906
+ const MAX_LOG_BYTES = 1e6;
907
+ const MAX_TRUNCATED_MESSAGE_CHARS = 15e4;
908
+ const TRUNCATED_LOG_KEYS = [
909
+ "ddsource",
910
+ "ddtags",
911
+ "hostname",
912
+ "service",
913
+ "status",
914
+ "timestamp",
915
+ "level",
916
+ "context",
917
+ "traceId",
918
+ "appName",
919
+ "environment",
920
+ "env",
921
+ "version",
922
+ "logger",
923
+ "dd.trace_id",
924
+ "dd.span_id"
925
+ ];
926
+ const STATUS_MAP = {
927
+ fatal: "critical",
928
+ critical: "critical",
929
+ error: "error",
930
+ warn: "warn",
931
+ warning: "warn",
932
+ info: "info",
933
+ debug: "debug",
934
+ trace: "debug",
935
+ verbose: "debug"
936
+ };
937
+ const MAX_UINT64 = (1n << 64n) - 1n;
938
+ const DASHED_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
939
+ const HEX = /^[0-9a-f]+$/i;
940
+ const DIGITS = /^\d+$/;
941
+ /**
942
+ * Datadog correlates on a decimal 64-bit id. Accepts:
943
+ * - a dashed UUID (logixia's default trace id): dashes stripped, then 32-hex;
944
+ * - 32 hex chars (W3C trace id) or 16 hex chars containing a-f: low 64 bits as decimal;
945
+ * - an all-digit string: already decimal (dd-trace's native format), passed through.
946
+ * An all-digit string is ambiguous when it is 16 or 32 long: 32 is read as hex (a
947
+ * decimal id has at most 20 digits), 16 is read as decimal.
948
+ * Anything else yields the raw string for trace ids (`passthrough`) and nothing for span ids.
949
+ */
950
+ function toDatadogId(id, passthrough) {
951
+ if (typeof id !== "string" || id === "") return void 0;
952
+ const fallback = passthrough ? id : void 0;
953
+ const compact = DASHED_UUID.test(id) ? id.replaceAll("-", "") : id;
954
+ const isHexId = HEX.test(compact) && (compact.length === 32 || compact.length === 16);
955
+ if (DIGITS.test(compact) && compact.length !== 32) {
956
+ const value = BigInt(compact);
957
+ return value <= MAX_UINT64 ? value.toString() : fallback;
958
+ }
959
+ if (isHexId) return BigInt(`0x${compact.slice(-16)}`).toString();
960
+ return fallback;
961
+ }
962
+ /** Datadog tags: no commas or whitespace, lowercase, at most 200 chars. */
963
+ function tag(key, value) {
964
+ return `${key}:${value}`.replaceAll(/[\s,]+/g, "_").toLowerCase().slice(0, 200);
965
+ }
892
966
  /**
893
- * Sends log entries and metrics to Datadog via the Logs and Metrics HTTP APIs.
967
+ * Sends log entries and metrics to Datadog via the Logs (v2) and Metrics HTTP APIs.
894
968
  *
895
969
  * The API key is transmitted via the `DD-API-KEY` request header — never in the URL.
896
970
  *
@@ -916,29 +990,68 @@ var DataDogTransport = class extends AnalyticsTransport {
916
990
  }
917
991
  }
918
992
  async sendEntry(entry) {
919
- const promises = [];
920
- if (this.datadogConfig.enableLogs !== false) promises.push(this.sendLog(entry));
921
- if (this.datadogConfig.enableMetrics) promises.push(this.sendMetric(entry));
922
- if (this.datadogConfig.enableTraces && entry.traceId) promises.push(this.sendTrace(entry));
923
- await Promise.all(promises);
993
+ await this.sendBatch([entry]);
924
994
  }
995
+ /**
996
+ * Logs go first, chunk by chunk; metrics follow once every log chunk is out.
997
+ * A failure part-way throws PartialBatchError naming only the entries that were
998
+ * not delivered, so a retry never re-posts a chunk Datadog already accepted.
999
+ */
925
1000
  async sendBatch(entries) {
926
- const promises = [];
927
- if (this.datadogConfig.enableLogs !== false) promises.push(this.sendLogBatch(entries));
928
- if (this.datadogConfig.enableMetrics) promises.push(this.sendMetricBatch(entries));
929
- if (this.datadogConfig.enableTraces) {
930
- const entriesWithTraces = entries.filter((e) => e.traceId);
931
- if (entriesWithTraces.length > 0) promises.push(this.sendTraceBatch(entriesWithTraces));
1001
+ if (this.datadogConfig.enableLogs !== false) await this.sendLogBatch(entries);
1002
+ if (this.datadogConfig.enableMetrics) try {
1003
+ await this.sendMetricBatch(entries);
1004
+ } catch (error) {
1005
+ if (this.datadogConfig.enableLogs === false) throw error;
1006
+ throw new PartialBatchError(error.message, [], { cause: error });
932
1007
  }
933
- await Promise.all(promises);
934
1008
  }
935
1009
  async cleanup() {}
936
- async sendLog(entry) {
937
- await this.sendLogBatch([entry]);
938
- }
939
1010
  async sendLogBatch(entries) {
940
- const payload = { logs: entries.map((entry) => this.transformToDataDogLog(entry)) };
941
- await this.makeLogsRequest("/v1/input", payload);
1011
+ const items = entries.map((entry) => ({
1012
+ entry,
1013
+ json: this.serializeLog(entry)
1014
+ }));
1015
+ const chunks = this.chunkLogs(items);
1016
+ for (let i = 0; i < chunks.length; i += 1) {
1017
+ const chunk$1 = chunks[i];
1018
+ try {
1019
+ await this.makeLogsRequest("/api/v2/logs", `[${chunk$1.map((item) => item.json).join(",")}]`);
1020
+ } catch (error) {
1021
+ if (i === 0) throw error;
1022
+ const unsent = chunks.slice(i).flatMap((c) => c.map((item) => item.entry));
1023
+ throw new PartialBatchError(error.message, unsent, { cause: error });
1024
+ }
1025
+ }
1026
+ }
1027
+ /** Splits into requests of at most 1000 entries and about 5MB of JSON. */
1028
+ chunkLogs(items) {
1029
+ const chunks = [];
1030
+ let current = [];
1031
+ let currentBytes = 2;
1032
+ for (const item of items) {
1033
+ const bytes = Buffer.byteLength(item.json) + 1;
1034
+ if (current.length > 0 && (current.length >= MAX_LOGS_PER_REQUEST || currentBytes + bytes > MAX_LOGS_PAYLOAD_BYTES)) {
1035
+ chunks.push(current);
1036
+ current = [];
1037
+ currentBytes = 2;
1038
+ }
1039
+ current.push(item);
1040
+ currentBytes += bytes;
1041
+ }
1042
+ if (current.length > 0) chunks.push(current);
1043
+ return chunks;
1044
+ }
1045
+ /** Serializes once. A log over 1MB keeps its identity and correlation fields but loses its attributes and most of its message. */
1046
+ serializeLog(entry) {
1047
+ const log = this.transformToDataDogLog(entry);
1048
+ const json = stringifyForLog(log);
1049
+ if (Buffer.byteLength(json) <= MAX_LOG_BYTES) return json;
1050
+ const kept = {};
1051
+ for (const key of TRUNCATED_LOG_KEYS) if (log[key] !== void 0) kept[key] = log[key];
1052
+ kept["message"] = String(log["message"]).slice(0, MAX_TRUNCATED_MESSAGE_CHARS);
1053
+ kept["truncated"] = true;
1054
+ return stringifyForLog(kept);
942
1055
  }
943
1056
  async sendMetric(entry) {
944
1057
  await this.sendMetricBatch([entry]);
@@ -947,29 +1060,35 @@ var DataDogTransport = class extends AnalyticsTransport {
947
1060
  const payload = { series: entries.map((entry) => this.transformToDataDogMetric(entry)) };
948
1061
  await this.makeMetricsRequest("/api/v1/series", payload);
949
1062
  }
950
- async sendTrace(entry) {
951
- await this.sendLog(entry);
952
- }
953
- async sendTraceBatch(entries) {
954
- await this.sendLogBatch(entries);
955
- }
956
1063
  transformToDataDogLog(entry) {
1064
+ var _entry$data, _entry$data2;
1065
+ const transformed = this.transformEntry(entry);
1066
+ const service = this.datadogConfig.service || "logixia";
1067
+ const env = this.datadogConfig.env || "production";
1068
+ const version = this.datadogConfig.version;
1069
+ const tags = [
1070
+ tag("env", env),
1071
+ tag("service", service),
1072
+ ...version ? [tag("version", version)] : []
1073
+ ];
1074
+ const spanId = toDatadogId(((_entry$data = entry.data) === null || _entry$data === void 0 ? void 0 : _entry$data["spanId"]) ?? ((_entry$data2 = entry.data) === null || _entry$data2 === void 0 ? void 0 : _entry$data2["span_id"]), false);
1075
+ const traceId = toDatadogId(entry.traceId, true);
957
1076
  return {
958
- ...this.transformEntry(entry),
959
- "dd.trace_id": entry.traceId,
960
- "dd.span_id": this.generateSpanId(),
961
- host: this.getHostname(),
962
- source: "nodejs",
963
- timestamp: entry.timestamp.toISOString(),
964
- level: this.mapLogLevel(entry.level),
1077
+ ...transformed,
1078
+ ddsource: "nodejs",
1079
+ ddtags: tags.join(","),
1080
+ hostname: this.getHostname(),
1081
+ service,
1082
+ status: this.mapLogLevel(entry.level),
965
1083
  message: entry.message,
966
- service: this.datadogConfig.service || "logixia",
967
- version: this.datadogConfig.version,
968
- env: this.datadogConfig.env || "production",
1084
+ env,
1085
+ version,
969
1086
  logger: {
970
1087
  name: "logixia",
971
1088
  thread_name: "main"
972
- }
1089
+ },
1090
+ ...traceId && { "dd.trace_id": traceId },
1091
+ ...traceId && spanId && { "dd.span_id": spanId }
973
1092
  };
974
1093
  }
975
1094
  reservedKeys() {
@@ -995,58 +1114,29 @@ var DataDogTransport = class extends AnalyticsTransport {
995
1114
  type: "count",
996
1115
  host: this.getHostname(),
997
1116
  tags: [
998
- `level:${entry.level}`,
999
- `service:${this.datadogConfig.service || "logixia"}`,
1000
- `env:${this.datadogConfig.env || "production"}`,
1001
- ...entry.context ? [`context:${entry.context}`] : [],
1002
- ...entry.appName ? [`app:${entry.appName}`] : []
1117
+ tag("level", entry.level),
1118
+ tag("service", this.datadogConfig.service || "logixia"),
1119
+ tag("env", this.datadogConfig.env || "production"),
1120
+ ...entry.context ? [tag("context", entry.context)] : [],
1121
+ ...entry.appName ? [tag("app", entry.appName)] : []
1003
1122
  ]
1004
1123
  };
1005
1124
  }
1006
- transformToDataDogTrace(entry) {
1007
- return {
1008
- trace_id: entry.traceId,
1009
- span_id: this.generateSpanId(),
1010
- parent_id: null,
1011
- name: "log.entry",
1012
- service: this.datadogConfig.service || "logixia",
1013
- resource: entry.message,
1014
- type: "log",
1015
- start: entry.timestamp.getTime() * 1e6,
1016
- duration: 1e6,
1017
- meta: {
1018
- "log.level": entry.level,
1019
- "log.message": entry.message,
1020
- ...entry.context && { "log.context": entry.context },
1021
- ...entry.data && Object.keys(entry.data).reduce((acc, key) => {
1022
- acc[`log.${key}`] = String(entry.data[key]);
1023
- return acc;
1024
- }, {})
1025
- },
1026
- metrics: { _sampling_priority_v1: 1 }
1027
- };
1028
- }
1029
1125
  mapLogLevel(level) {
1030
- return {
1031
- error: "error",
1032
- warn: "warn",
1033
- warning: "warn",
1034
- info: "info",
1035
- debug: "debug",
1036
- trace: "trace",
1037
- verbose: "debug"
1038
- }[level.toLowerCase()] || "info";
1039
- }
1040
- async makeLogsRequest(endpoint, data) {
1126
+ return STATUS_MAP[level.toLowerCase()] || "info";
1127
+ }
1128
+ async makeLogsRequest(endpoint, json) {
1041
1129
  const url = `${this.logsUrl}${endpoint}`;
1042
1130
  try {
1131
+ const compress = this.datadogConfig.compress === true;
1043
1132
  const response = await fetch(url, {
1044
1133
  method: "POST",
1045
1134
  headers: {
1046
1135
  "Content-Type": "application/json",
1047
- "DD-API-KEY": this.datadogConfig.apiKey
1136
+ "DD-API-KEY": this.datadogConfig.apiKey,
1137
+ ...compress && { "Content-Encoding": "gzip" }
1048
1138
  },
1049
- body: stringifyForLog(data)
1139
+ body: compress ? await gzipAsync(json) : json
1050
1140
  });
1051
1141
  if (!response.ok) throw new Error(`DataDog Logs API error: ${response.status} ${response.statusText}`);
1052
1142
  } catch (error) {
@@ -1069,9 +1159,6 @@ var DataDogTransport = class extends AnalyticsTransport {
1069
1159
  throw new Error(`Failed to send metrics to DataDog: ${error.message}`, { cause: error });
1070
1160
  }
1071
1161
  }
1072
- generateSpanId() {
1073
- return Math.floor(Math.random() * 0xffffffffffff).toString(16);
1074
- }
1075
1162
  getHostname() {
1076
1163
  try {
1077
1164
  return __require("node:os").hostname();
@@ -2564,5 +2651,5 @@ var TransportManager = class extends EventEmitter {
2564
2651
  };
2565
2652
 
2566
2653
  //#endregion
2567
- export { FileTransport as a, DatabaseTransport as c, GoogleAnalyticsTransport as i, ConsoleTransport as l, SegmentTransport as n, DataDogTransport as o, MixpanelTransport as r, AnalyticsTransport as s, TransportManager as t, stripControlChars as u };
2568
- //# sourceMappingURL=transport.manager-CpdVHEGP.mjs.map
2654
+ export { FileTransport as a, DatabaseTransport as c, stripControlChars as d, GoogleAnalyticsTransport as i, BufferCap as l, SegmentTransport as n, DataDogTransport as o, MixpanelTransport as r, AnalyticsTransport as s, TransportManager as t, ConsoleTransport as u };
2655
+ //# sourceMappingURL=transport.manager-BiuCAJd2.mjs.map