{"_id":"@alexpricedev/log-digest","name":"@alexpricedev/log-digest","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@alexpricedev/log-digest","version":"0.1.0","description":"In-memory log buffer with pluggable digest delivery for Bun apps","type":"module","module":"src/index.ts","types":"src/index.ts","exports":{".":{"types":"./src/index.ts","import":"./src/index.ts","default":"./src/index.ts"}},"scripts":{"test":"bun test","typecheck":"tsc --noEmit"},"publishConfig":{"access":"public"},"engines":{"bun":">=1.0.0"},"license":"MIT","author":{"name":"Alex Price"},"repository":{"type":"git","url":"git+https://github.com/alexpricedev/log-digest.git"},"bugs":{"url":"https://github.com/alexpricedev/log-digest/issues"},"homepage":"https://github.com/alexpricedev/log-digest#readme","keywords":["logging","log","digest","bun","buffer","hourly","email","slack"],"devDependencies":{"@types/bun":"latest","typescript":"^5.4.0"},"_id":"@alexpricedev/log-digest@0.1.0","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-Ej/W6Hsaq1DvHP1CmSKSSzBjGM9idfzkr7mtVuOytOzZfnoWbm3Uc6/NryPM/PjnONVkvnBNntEelXRMkBF9Cw==","shasum":"00cc701cc1e7b6493bce740505b2a38ccbb4192f","tarball":"https://registry.npmjs.org/@alexpricedev/log-digest/-/log-digest-0.1.0.tgz","fileCount":6,"unpackedSize":14646,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDzde8jdtqfHw4jyVD5sklxvzb7GJ0NqjUtZuHK4BN/SwIgb4n4RhNoo72Z+GIcWwt0A+KS1q9u/heXA8WJXe9+b/I="}]},"_npmUser":{"name":"alexpricedev","email":"npm@alexprice.dev"},"directories":{},"maintainers":[{"name":"alexpricedev","email":"npm@alexprice.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/log-digest_0.1.0_1776854718234_0.40749201263287427"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-22T10:45:18.175Z","0.1.0":"2026-04-22T10:45:18.358Z","modified":"2026-04-22T10:45:18.547Z"},"maintainers":[{"name":"alexpricedev","email":"npm@alexprice.dev"}],"description":"In-memory log buffer with pluggable digest delivery for Bun apps","homepage":"https://github.com/alexpricedev/log-digest#readme","keywords":["logging","log","digest","bun","buffer","hourly","email","slack"],"repository":{"type":"git","url":"git+https://github.com/alexpricedev/log-digest.git"},"author":{"name":"Alex Price"},"bugs":{"url":"https://github.com/alexpricedev/log-digest/issues"},"license":"MIT","readme":"# @alexpricedev/log-digest\n\nIn-memory log buffer with a pluggable digest sink. Buffers `error` / `warn` / `info` entries as they happen, then — on an interval you control — hands them off to a sink of your choosing (email, Slack, webhook, anywhere). Zero runtime dependencies. Bun only.\n\n## Install\n\n```sh\nbun add @alexpricedev/log-digest\n```\n\n## Quick start\n\n```ts\nimport {\n  logError,\n  logInfo,\n  startDigestScheduler,\n  type DigestSink,\n} from \"@alexpricedev/log-digest\";\n\nconst emailSink: DigestSink = {\n  async send({ format, summary, period }) {\n    await sendEmail({\n      to: \"ops@example.com\",\n      subject: `Log digest — ${period} — ${summary.errorCount} errors`,\n      html: format.html,\n      text: format.text,\n    });\n  },\n};\n\nstartDigestScheduler({ sink: emailSink });\n\nlogInfo(\"boot\", \"server started\");\nlogError(\"payments\", \"charge failed\", { orderId: \"abc123\" });\n```\n\nEntries are also mirrored to `console.log` / `console.warn` / `console.error` as they happen, so you still see them in your normal stdout/stderr stream.\n\n## How it works\n\n- Every `log*` call appends a `LogEntry` to an in-memory buffer.\n- Every `intervalMs` (default: 1 hour) the scheduler calls `drainLogs()` and hands the result to your sink.\n- `formatDigest()` renders a styled HTML table + a plain-text summary, both available on `payload.format`.\n- If your sink throws, the error and the logs that would have been sent are written to `console.error`. The scheduler keeps running.\n\n## API\n\n### Logging\n\n```ts\nlog(level, category, message, data?): void\nlogError(category, message, data?): void\nlogWarn(category, message, data?): void\nlogInfo(category, message, data?): void\n```\n\n### Buffer\n\n```ts\ndrainLogs(): LogEntry[]     // returns + clears the buffer\ngetBufferSize(): number\n```\n\n### Formatting\n\n```ts\nformatDigest(logs: LogEntry[]): { html: string; text: string }\n```\n\n### Scheduler\n\n```ts\nstartDigestScheduler({\n  sink,                 // DigestSink\n  intervalMs?: number,  // default 3_600_000 (1h)\n  startupDelayMs?: number, // default 5000 (ms after start for a first flush; 0 disables)\n}): void\n\nstopDigestScheduler(): void\nprocessDigest(sink: DigestSink): Promise<void>  // manual flush\n```\n\n### Sink contract\n\n```ts\ninterface DigestSink {\n  send(payload: DigestPayload): Promise<void>;\n}\n\ninterface DigestPayload {\n  logs: LogEntry[];\n  summary: { errorCount: number; warnCount: number; infoCount: number };\n  period: string;                            // e.g. \"2026-04-21 18:00 UTC\"\n  format: { html: string; text: string };\n}\n```\n\n## Example sinks\n\n### Slack webhook\n\n```ts\nconst slackSink: DigestSink = {\n  async send({ summary, period, format }) {\n    await fetch(process.env.SLACK_WEBHOOK_URL!, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify({\n        text: `Log digest — ${period}\\n${summary.errorCount} errors, ${summary.warnCount} warnings, ${summary.infoCount} info\\n\\n${format.text}`,\n      }),\n    });\n  },\n};\n```\n\n### HTTP webhook (raw payload)\n\n```ts\nconst httpSink: DigestSink = {\n  async send(payload) {\n    await fetch(\"https://hooks.example.com/logs\", {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify(payload),\n    });\n  },\n};\n```\n\n## Testing in your app\n\nThe buffer is module-level singleton state. In tests, call `drainLogs()` in `beforeEach`/`afterEach` for isolation. Pass a recording sink to `processDigest` to assert on payload contents without starting the scheduler.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-3c71014d4b96eb3a981965feee5e1664"}