evlog 2.16.0 → 2.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +102 -9
  2. package/dist/adapters/axiom.d.mts +1 -1
  3. package/dist/adapters/axiom.mjs +4 -2
  4. package/dist/adapters/axiom.mjs.map +1 -1
  5. package/dist/adapters/better-stack.d.mts +1 -1
  6. package/dist/adapters/better-stack.mjs +4 -2
  7. package/dist/adapters/better-stack.mjs.map +1 -1
  8. package/dist/adapters/datadog.d.mts +1 -1
  9. package/dist/adapters/datadog.mjs +4 -2
  10. package/dist/adapters/datadog.mjs.map +1 -1
  11. package/dist/adapters/fs.d.mts +64 -2
  12. package/dist/adapters/fs.d.mts.map +1 -1
  13. package/dist/adapters/fs.mjs +222 -3
  14. package/dist/adapters/fs.mjs.map +1 -1
  15. package/dist/adapters/hyperdx.d.mts +1 -1
  16. package/dist/adapters/hyperdx.mjs +1 -1
  17. package/dist/adapters/memory.d.mts +116 -0
  18. package/dist/adapters/memory.d.mts.map +1 -0
  19. package/dist/adapters/memory.mjs +191 -0
  20. package/dist/adapters/memory.mjs.map +1 -0
  21. package/dist/adapters/otlp.d.mts +1 -1
  22. package/dist/adapters/otlp.mjs +6 -4
  23. package/dist/adapters/otlp.mjs.map +1 -1
  24. package/dist/adapters/posthog.d.mts +1 -1
  25. package/dist/adapters/posthog.mjs +4 -2
  26. package/dist/adapters/posthog.mjs.map +1 -1
  27. package/dist/adapters/sentry.d.mts +1 -1
  28. package/dist/adapters/sentry.mjs +5 -3
  29. package/dist/adapters/sentry.mjs.map +1 -1
  30. package/dist/ai/index.d.mts +1 -1
  31. package/dist/{audit-pV5aLGP0.mjs → audit-BUI3af4w.mjs} +121 -53
  32. package/dist/audit-BUI3af4w.mjs.map +1 -0
  33. package/dist/{audit-X1uUukm3.d.mts → audit-DVdkntSO.d.mts} +76 -5
  34. package/dist/audit-DVdkntSO.d.mts.map +1 -0
  35. package/dist/better-auth/index.d.mts +14 -7
  36. package/dist/better-auth/index.d.mts.map +1 -1
  37. package/dist/better-auth/index.mjs +11 -1
  38. package/dist/better-auth/index.mjs.map +1 -1
  39. package/dist/browser.d.mts +1 -1
  40. package/dist/{define-CuXOqecD.d.mts → define-D-BVMf2l.d.mts} +3 -3
  41. package/dist/{define-CuXOqecD.d.mts.map → define-D-BVMf2l.d.mts.map} +1 -1
  42. package/dist/define-D6OJdSUH.mjs.map +1 -1
  43. package/dist/{dist-BIlS38vi.mjs → dist-H3GIh-KK.mjs} +1 -1
  44. package/dist/{dist-BIlS38vi.mjs.map → dist-H3GIh-KK.mjs.map} +1 -1
  45. package/dist/{drain-ByWUeOQC.mjs → drain-7n3K6kPe.mjs} +6 -49
  46. package/dist/drain-7n3K6kPe.mjs.map +1 -0
  47. package/dist/elysia/index.d.mts +2 -2
  48. package/dist/elysia/index.mjs +2 -2
  49. package/dist/{enricher-Dy06T17G.mjs → enricher-N0erZS87.mjs} +2 -2
  50. package/dist/{enricher-Dy06T17G.mjs.map → enricher-N0erZS87.mjs.map} +1 -1
  51. package/dist/{enricher-DYTr9I16.d.mts → enricher-UW9npoB2.d.mts} +2 -2
  52. package/dist/{enricher-DYTr9I16.d.mts.map → enricher-UW9npoB2.d.mts.map} +1 -1
  53. package/dist/enrichers.d.mts +2 -2
  54. package/dist/enrichers.mjs +1 -1
  55. package/dist/{error-Cpc7RVz6.d.mts → error-CVtn5U7b.d.mts} +2 -2
  56. package/dist/{error-Cpc7RVz6.d.mts.map → error-CVtn5U7b.d.mts.map} +1 -1
  57. package/dist/error.d.mts +1 -1
  58. package/dist/{errors-prnQ3kES.d.mts → errors-dEMNQCiL.d.mts} +2 -2
  59. package/dist/{errors-prnQ3kES.d.mts.map → errors-dEMNQCiL.d.mts.map} +1 -1
  60. package/dist/{event-DcHmEm3O.mjs → event-1BMl7o0k.mjs} +1 -1
  61. package/dist/{event-DcHmEm3O.mjs.map → event-1BMl7o0k.mjs.map} +1 -1
  62. package/dist/express/index.d.mts +3 -3
  63. package/dist/express/index.d.mts.map +1 -1
  64. package/dist/express/index.mjs +5 -6
  65. package/dist/express/index.mjs.map +1 -1
  66. package/dist/fastify/index.d.mts +9 -4
  67. package/dist/fastify/index.d.mts.map +1 -1
  68. package/dist/fastify/index.mjs +10 -8
  69. package/dist/fastify/index.mjs.map +1 -1
  70. package/dist/{fork-DPN8aL8O.mjs → fork-Bga8x-X4.mjs} +4 -3
  71. package/dist/fork-Bga8x-X4.mjs.map +1 -0
  72. package/dist/hono/index.d.mts +2 -2
  73. package/dist/hono/index.mjs +1 -1
  74. package/dist/http-B6YgAhyN.mjs +82 -0
  75. package/dist/http-B6YgAhyN.mjs.map +1 -0
  76. package/dist/http.d.mts +1 -1
  77. package/dist/http.mjs +1 -0
  78. package/dist/http.mjs.map +1 -1
  79. package/dist/index-ZSRQP_BI.d.mts +213 -0
  80. package/dist/index-ZSRQP_BI.d.mts.map +1 -0
  81. package/dist/index.d.mts +9 -8
  82. package/dist/index.mjs +210 -2
  83. package/dist/index.mjs.map +1 -0
  84. package/dist/{integration-DSZPbI9N.mjs → integration-Dhig7ae6.mjs} +2 -2
  85. package/dist/{integration-DSZPbI9N.mjs.map → integration-Dhig7ae6.mjs.map} +1 -1
  86. package/dist/{logger-U8lgdc9x.d.mts → logger-CTcvd5Cc.d.mts} +7 -3
  87. package/dist/logger-CTcvd5Cc.d.mts.map +1 -0
  88. package/dist/logger.d.mts +2 -2
  89. package/dist/logger.mjs +2 -2
  90. package/dist/{middleware-CAQHJRN1.d.mts → middleware-31KhtiEF.d.mts} +2 -2
  91. package/dist/{middleware-CAQHJRN1.d.mts.map → middleware-31KhtiEF.d.mts.map} +1 -1
  92. package/dist/nestjs/index.d.mts +2 -2
  93. package/dist/nestjs/index.d.mts.map +1 -1
  94. package/dist/nestjs/index.mjs +4 -5
  95. package/dist/nestjs/index.mjs.map +1 -1
  96. package/dist/next/client.d.mts +1 -1
  97. package/dist/next/index.d.mts +6 -5
  98. package/dist/next/index.d.mts.map +1 -1
  99. package/dist/next/index.mjs +4 -4
  100. package/dist/next/index.mjs.map +1 -1
  101. package/dist/next/instrumentation.d.mts +1 -1
  102. package/dist/next/instrumentation.mjs +1 -1
  103. package/dist/next/instrumentation.mjs.map +1 -1
  104. package/dist/next/stream.d.mts +29 -0
  105. package/dist/next/stream.d.mts.map +1 -0
  106. package/dist/next/stream.mjs +78 -0
  107. package/dist/next/stream.mjs.map +1 -0
  108. package/dist/nitro/errorHandler.mjs +1 -1
  109. package/dist/nitro/module.d.mts +2 -2
  110. package/dist/nitro/module.d.mts.map +1 -1
  111. package/dist/nitro/module.mjs +7 -2
  112. package/dist/nitro/module.mjs.map +1 -1
  113. package/dist/nitro/plugin.mjs +13 -3
  114. package/dist/nitro/plugin.mjs.map +1 -1
  115. package/dist/nitro/v3/errorHandler.mjs +2 -2
  116. package/dist/nitro/v3/index.d.mts +2 -2
  117. package/dist/nitro/v3/module.d.mts +1 -1
  118. package/dist/nitro/v3/module.d.mts.map +1 -1
  119. package/dist/nitro/v3/module.mjs +9 -4
  120. package/dist/nitro/v3/module.mjs.map +1 -1
  121. package/dist/nitro/v3/plugin.mjs +5 -4
  122. package/dist/nitro/v3/plugin.mjs.map +1 -1
  123. package/dist/nitro/v3/useLogger.d.mts +1 -1
  124. package/dist/{nitro-C6Bd682U.d.mts → nitro-BRddgqSb.d.mts} +2 -2
  125. package/dist/{nitro-C6Bd682U.d.mts.map → nitro-BRddgqSb.d.mts.map} +1 -1
  126. package/dist/{nitro-DavLelNz.mjs → nitro-DErMq_Zj.mjs} +1 -1
  127. package/dist/{nitro-DavLelNz.mjs.map → nitro-DErMq_Zj.mjs.map} +1 -1
  128. package/dist/nitroConfigBridge-NbFn-sIK.mjs +157 -0
  129. package/dist/nitroConfigBridge-NbFn-sIK.mjs.map +1 -0
  130. package/dist/nodeResponse-BkkionWl.mjs +42 -0
  131. package/dist/nodeResponse-BkkionWl.mjs.map +1 -0
  132. package/dist/nuxt/module.d.mts +35 -4
  133. package/dist/nuxt/module.d.mts.map +1 -1
  134. package/dist/nuxt/module.mjs +11 -4
  135. package/dist/nuxt/module.mjs.map +1 -1
  136. package/dist/orpc/index.d.mts +115 -0
  137. package/dist/orpc/index.d.mts.map +1 -0
  138. package/dist/orpc/index.mjs +144 -0
  139. package/dist/orpc/index.mjs.map +1 -0
  140. package/dist/package-B23bR3tK.mjs +7 -0
  141. package/dist/package-B23bR3tK.mjs.map +1 -0
  142. package/dist/{parseError-B-dKF6Fd.d.mts → parseError-D4PIxEWo.d.mts} +2 -2
  143. package/dist/parseError-D4PIxEWo.d.mts.map +1 -0
  144. package/dist/react-router/index.d.mts +2 -2
  145. package/dist/react-router/index.mjs +2 -2
  146. package/dist/{routes-B48wm7Pb.mjs → routes-CnIgYWf8.mjs} +1 -1
  147. package/dist/{routes-B48wm7Pb.mjs.map → routes-CnIgYWf8.mjs.map} +1 -1
  148. package/dist/runtime/client/log.d.mts +1 -1
  149. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +28 -12
  150. package/dist/runtime/server/routes/_evlog/ingest.post.mjs.map +1 -1
  151. package/dist/runtime/server/routes/_evlog/stream-info.get.d.mts +18 -0
  152. package/dist/runtime/server/routes/_evlog/stream-info.get.d.mts.map +1 -0
  153. package/dist/runtime/server/routes/_evlog/stream-info.get.mjs +28 -0
  154. package/dist/runtime/server/routes/_evlog/stream-info.get.mjs.map +1 -0
  155. package/dist/runtime/server/useLogger.d.mts +1 -1
  156. package/dist/runtime/utils/parseError.d.mts +2 -2
  157. package/dist/{severity-BYWZ96Sb.mjs → severity-R5Egq3qz.mjs} +1 -1
  158. package/dist/{severity-BYWZ96Sb.mjs.map → severity-R5Egq3qz.mjs.map} +1 -1
  159. package/dist/{storage-BT-3fT1-.mjs → storage-BNubsWwz.mjs} +2 -1
  160. package/dist/{storage-BT-3fT1-.mjs.map → storage-BNubsWwz.mjs.map} +1 -1
  161. package/dist/stream.d.mts +185 -0
  162. package/dist/stream.d.mts.map +1 -0
  163. package/dist/stream.mjs +374 -0
  164. package/dist/stream.mjs.map +1 -0
  165. package/dist/sveltekit/index.d.mts +3 -3
  166. package/dist/sveltekit/index.d.mts.map +1 -1
  167. package/dist/sveltekit/index.mjs +44 -11
  168. package/dist/sveltekit/index.mjs.map +1 -1
  169. package/dist/toolkit.d.mts +43 -8
  170. package/dist/toolkit.d.mts.map +1 -1
  171. package/dist/toolkit.mjs +11 -10
  172. package/dist/types.d.mts +2 -2
  173. package/dist/{useLogger-CoNgTjp5.d.mts → useLogger-CqvH6qOf.d.mts} +2 -2
  174. package/dist/{useLogger-CoNgTjp5.d.mts.map → useLogger-CqvH6qOf.d.mts.map} +1 -1
  175. package/dist/{utils-Db4qhBWn.d.mts → utils-DxqvIOyR.d.mts} +12 -3
  176. package/dist/{utils-Db4qhBWn.d.mts.map → utils-DxqvIOyR.d.mts.map} +1 -1
  177. package/dist/utils.d.mts +1 -1
  178. package/dist/utils.mjs +10 -1
  179. package/dist/utils.mjs.map +1 -1
  180. package/dist/vite/index.d.mts +1 -1
  181. package/dist/workers.d.mts +1 -1
  182. package/dist/workers.mjs +1 -1
  183. package/package.json +64 -15
  184. package/dist/audit-X1uUukm3.d.mts.map +0 -1
  185. package/dist/audit-pV5aLGP0.mjs.map +0 -1
  186. package/dist/drain-ByWUeOQC.mjs.map +0 -1
  187. package/dist/fork-DPN8aL8O.mjs.map +0 -1
  188. package/dist/logger-U8lgdc9x.d.mts.map +0 -1
  189. package/dist/nitroConfigBridge-aZ1e5upQ.mjs +0 -92
  190. package/dist/nitroConfigBridge-aZ1e5upQ.mjs.map +0 -1
  191. package/dist/parseError-B-dKF6Fd.d.mts.map +0 -1
@@ -1,6 +1,8 @@
1
- import { a as resolveAdapterConfig, t as defineDrain } from "../drain-ByWUeOQC.mjs";
1
+ import { i as resolveAdapterConfig, t as defineDrain } from "../drain-7n3K6kPe.mjs";
2
2
  import { join, sep } from "node:path";
3
- import { appendFile, mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
3
+ import { appendFile, mkdir, open, readdir, stat, unlink, writeFile } from "node:fs/promises";
4
+ import { createReadStream } from "node:fs";
5
+ import { createInterface } from "node:readline";
4
6
  //#region src/adapters/fs.ts
5
7
  const FS_FIELDS = [
6
8
  {
@@ -112,7 +114,224 @@ function createFsDrain(overrides) {
112
114
  send: writeBatchToFs
113
115
  });
114
116
  }
117
+ function isLogFilename(filename) {
118
+ return /^\d{4}-\d{2}-\d{2}(\.\d+)?\.jsonl$/.test(filename);
119
+ }
120
+ function compareLogFiles(a, b) {
121
+ const pa = parseLogFilename(a);
122
+ const pb = parseLogFilename(b);
123
+ return pa.date.localeCompare(pb.date) || pa.index - pb.index;
124
+ }
125
+ function normalizeTimestamp(value) {
126
+ if (!value) return void 0;
127
+ const ts = (value instanceof Date ? value : new Date(value)).getTime();
128
+ return Number.isNaN(ts) ? void 0 : ts;
129
+ }
130
+ function buildFilter(options) {
131
+ const since = normalizeTimestamp(options.since);
132
+ const until = normalizeTimestamp(options.until);
133
+ const levels = options.level ? new Set(Array.isArray(options.level) ? options.level : [options.level]) : void 0;
134
+ const custom = options.filter;
135
+ return (event) => {
136
+ if (levels && !levels.has(event.level)) return false;
137
+ if (since !== void 0 || until !== void 0) {
138
+ const ts = typeof event.timestamp === "string" ? Date.parse(event.timestamp) : NaN;
139
+ if (Number.isNaN(ts)) return false;
140
+ if (since !== void 0 && ts < since) return false;
141
+ if (until !== void 0 && ts > until) return false;
142
+ }
143
+ if (custom && !custom(event)) return false;
144
+ return true;
145
+ };
146
+ }
147
+ async function listLogFiles(dir) {
148
+ let files;
149
+ try {
150
+ files = await readdir(dir);
151
+ } catch {
152
+ return [];
153
+ }
154
+ return files.filter(isLogFilename).sort(compareLogFiles);
155
+ }
156
+ function fileDateMs(filename) {
157
+ const { date } = parseLogFilename(filename);
158
+ return date ? Date.parse(`${date}T00:00:00.000Z`) : NaN;
159
+ }
160
+ function fileWithinRange(filename, since, until) {
161
+ if (since === void 0 && until === void 0) return true;
162
+ const dayStart = fileDateMs(filename);
163
+ if (Number.isNaN(dayStart)) return true;
164
+ const dayEnd = dayStart + 1440 * 60 * 1e3 - 1;
165
+ if (since !== void 0 && dayEnd < since) return false;
166
+ if (until !== void 0 && dayStart > until) return false;
167
+ return true;
168
+ }
169
+ async function* iterateFile(filePath) {
170
+ const stream = createReadStream(filePath, { encoding: "utf-8" });
171
+ const rl = createInterface({
172
+ input: stream,
173
+ crlfDelay: Infinity
174
+ });
175
+ try {
176
+ for await (const line of rl) {
177
+ const trimmed = line.trim();
178
+ if (!trimmed) continue;
179
+ try {
180
+ yield JSON.parse(trimmed);
181
+ } catch {}
182
+ }
183
+ } finally {
184
+ rl.close();
185
+ stream.destroy();
186
+ }
187
+ }
188
+ /**
189
+ * Read past events from the local file system drain (NDJSON). Files are
190
+ * iterated in chronological order; events are yielded as they appear in
191
+ * each file.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * import { readFsLogs } from 'evlog/fs'
196
+ *
197
+ * for await (const event of readFsLogs({ since: '2026-01-01', level: 'error' })) {
198
+ * console.log(event)
199
+ * }
200
+ * ```
201
+ */
202
+ async function* readFsLogs(options = {}) {
203
+ const dir = options.dir ?? ".evlog/logs";
204
+ const sinceMs = normalizeTimestamp(options.since);
205
+ const untilMs = normalizeTimestamp(options.until);
206
+ const predicate = buildFilter(options);
207
+ const files = await listLogFiles(dir);
208
+ for (const filename of files) {
209
+ if (!fileWithinRange(filename, sinceMs, untilMs)) continue;
210
+ for await (const event of iterateFile(join(dir, filename))) if (predicate(event)) yield event;
211
+ }
212
+ }
213
+ async function safeStatSize(filePath) {
214
+ try {
215
+ return (await stat(filePath)).size;
216
+ } catch {
217
+ return 0;
218
+ }
219
+ }
220
+ async function readAppendedLines(filePath, fromOffset, carry) {
221
+ const size = await safeStatSize(filePath);
222
+ if (size <= fromOffset) return {
223
+ events: [],
224
+ offset: fromOffset,
225
+ carry
226
+ };
227
+ const handle = await open(filePath, "r");
228
+ try {
229
+ const length = size - fromOffset;
230
+ const buf = Buffer.alloc(length);
231
+ await handle.read(buf, 0, length, fromOffset);
232
+ const chunk = carry + buf.toString("utf-8");
233
+ const newlineIdx = chunk.lastIndexOf("\n");
234
+ if (newlineIdx === -1) return {
235
+ events: [],
236
+ offset: size,
237
+ carry: chunk
238
+ };
239
+ const complete = chunk.slice(0, newlineIdx);
240
+ const remainder = chunk.slice(newlineIdx + 1);
241
+ return {
242
+ events: complete.split("\n").map((l) => l.trim()).filter(Boolean),
243
+ offset: size,
244
+ carry: remainder
245
+ };
246
+ } finally {
247
+ await handle.close();
248
+ }
249
+ }
250
+ function delay(ms, signal) {
251
+ return new Promise((resolve) => {
252
+ const timer = setTimeout(() => {
253
+ if (signal) signal.removeEventListener("abort", onAbort);
254
+ resolve();
255
+ }, ms);
256
+ const onAbort = () => {
257
+ clearTimeout(timer);
258
+ resolve();
259
+ };
260
+ if (signal) {
261
+ if (signal.aborted) {
262
+ clearTimeout(timer);
263
+ resolve();
264
+ return;
265
+ }
266
+ signal.addEventListener("abort", onAbort, { once: true });
267
+ }
268
+ });
269
+ }
270
+ /**
271
+ * Follow the local file system drain in real time. Yields existing events
272
+ * (unless `fromEnd: true`) then keeps yielding new events as they are
273
+ * appended. Automatically picks up newly created daily files.
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * import { tailFsLogs } from 'evlog/fs'
278
+ *
279
+ * const ac = new AbortController()
280
+ * setTimeout(() => ac.abort(), 60_000)
281
+ *
282
+ * for await (const event of tailFsLogs({ signal: ac.signal })) {
283
+ * console.log('live:', event.action ?? event.message)
284
+ * }
285
+ * ```
286
+ */
287
+ async function* tailFsLogs(options = {}) {
288
+ const dir = options.dir ?? ".evlog/logs";
289
+ const interval = Math.max(50, options.pollIntervalMs ?? 500);
290
+ const { signal } = options;
291
+ const predicate = buildFilter(options);
292
+ const offsets = /* @__PURE__ */ new Map();
293
+ const carries = /* @__PURE__ */ new Map();
294
+ if (options.fromEnd) {
295
+ const files = await listLogFiles(dir);
296
+ for (const filename of files) {
297
+ offsets.set(filename, await safeStatSize(join(dir, filename)));
298
+ carries.set(filename, "");
299
+ }
300
+ } else {
301
+ for await (const event of readFsLogs(options)) {
302
+ if (signal?.aborted) return;
303
+ yield event;
304
+ }
305
+ const files = await listLogFiles(dir);
306
+ for (const filename of files) if (!offsets.has(filename)) {
307
+ offsets.set(filename, await safeStatSize(join(dir, filename)));
308
+ carries.set(filename, "");
309
+ }
310
+ }
311
+ while (true) {
312
+ if (signal?.aborted) return;
313
+ await delay(interval, signal);
314
+ if (signal?.aborted) return;
315
+ const files = await listLogFiles(dir);
316
+ for (const filename of files) {
317
+ if (!offsets.has(filename)) {
318
+ offsets.set(filename, 0);
319
+ carries.set(filename, "");
320
+ }
321
+ const { events, offset, carry: newCarry } = await readAppendedLines(join(dir, filename), offsets.get(filename), carries.get(filename) ?? "");
322
+ offsets.set(filename, offset);
323
+ carries.set(filename, newCarry);
324
+ for (const line of events) {
325
+ if (signal?.aborted) return;
326
+ try {
327
+ const event = JSON.parse(line);
328
+ if (predicate(event)) yield event;
329
+ } catch {}
330
+ }
331
+ }
332
+ }
333
+ }
115
334
  //#endregion
116
- export { createFsDrain, writeBatchToFs, writeToFs };
335
+ export { createFsDrain, readFsLogs, tailFsLogs, writeBatchToFs, writeToFs };
117
336
 
118
337
  //# sourceMappingURL=fs.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"fs.mjs","names":[],"sources":["../../src/adapters/fs.ts"],"sourcesContent":["import { appendFile, mkdir, readdir, stat, unlink, writeFile } from 'node:fs/promises'\nimport { join, sep } from 'node:path'\nimport type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineDrain } from '../shared/drain'\n\nexport interface FsConfig {\n /** Directory for log files. Default: `.evlog/logs` */\n dir: string\n /** Max number of log files to keep (auto-deletes oldest when exceeded) */\n maxFiles?: number\n /** Max bytes per file before rotating to a new suffixed file */\n maxSizePerFile?: number\n /** Pretty-print JSON instead of compact NDJSON */\n pretty: boolean\n}\n\nconst FS_FIELDS: ConfigField<FsConfig>[] = [\n { key: 'dir', env: ['NUXT_EVLOG_FS_DIR', 'EVLOG_FS_DIR'] },\n { key: 'maxFiles' },\n { key: 'maxSizePerFile' },\n { key: 'pretty' },\n]\n\nconst gitignoreWritten = new Set<string>()\n\nasync function ensureGitignore(dir: string): Promise<void> {\n const normalized = dir.replace(/[\\\\/]/g, sep)\n const segments = normalized.split(sep)\n const evlogIndex = segments.findIndex(s => s === '.evlog')\n const targetDir = evlogIndex !== -1 ? segments.slice(0, evlogIndex + 1).join(sep) : dir\n\n if (gitignoreWritten.has(targetDir)) return\n\n const gitignorePath = join(targetDir, '.gitignore')\n try {\n await stat(gitignorePath)\n } catch {\n await writeFile(gitignorePath, '*\\n', 'utf-8')\n }\n gitignoreWritten.add(targetDir)\n}\n\nfunction getDateString(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nasync function resolveFilePath(dir: string, maxSizePerFile?: number): Promise<string> {\n const date = getDateString()\n const basePath = join(dir, `${date}.jsonl`)\n\n if (!maxSizePerFile) return basePath\n\n try {\n const stats = await stat(basePath)\n if (stats.size < maxSizePerFile) return basePath\n } catch {\n return basePath\n }\n\n for (let i = 1; i < 1000; i++) {\n const rotatedPath = join(dir, `${date}.${i}.jsonl`)\n try {\n const stats = await stat(rotatedPath)\n if (stats.size < maxSizePerFile) return rotatedPath\n } catch {\n return rotatedPath\n }\n }\n\n return join(dir, `${date}.999.jsonl`)\n}\n\nfunction parseLogFilename(filename: string): { date: string; index: number } {\n const match = filename.match(/^(\\d{4}-\\d{2}-\\d{2})(?:\\.(\\d+))?\\.jsonl$/)\n if (!match) return { date: '', index: 0 }\n return { date: match[1], index: match[2] ? Number.parseInt(match[2], 10) : 0 }\n}\n\nasync function cleanupOldFiles(dir: string, maxFiles: number): Promise<void> {\n const files = await readdir(dir)\n const jsonlFiles = files.filter(f => f.endsWith('.jsonl')).sort((a, b) => {\n const pa = parseLogFilename(a)\n const pb = parseLogFilename(b)\n return pa.date.localeCompare(pb.date) || pa.index - pb.index\n })\n\n if (jsonlFiles.length <= maxFiles) return\n\n const toDelete = jsonlFiles.slice(0, jsonlFiles.length - maxFiles)\n await Promise.allSettled(toDelete.map(f => unlink(join(dir, f))))\n}\n\nexport async function writeToFs(event: WideEvent, config: FsConfig): Promise<void> {\n await writeBatchToFs([event], config)\n}\n\nexport async function writeBatchToFs(events: WideEvent[], config: FsConfig): Promise<void> {\n if (events.length === 0) return\n\n await mkdir(config.dir, { recursive: true })\n await ensureGitignore(config.dir)\n\n const filePath = await resolveFilePath(config.dir, config.maxSizePerFile)\n const lines = `${events\n .map(e => config.pretty ? JSON.stringify(e, null, 2) : JSON.stringify(e))\n .join('\\n') }\\n`\n\n await appendFile(filePath, lines, 'utf-8')\n\n if (config.maxFiles) {\n await cleanupOldFiles(config.dir, config.maxFiles)\n }\n}\n\n/**\n * Create a drain function that writes logs to the local file system as NDJSON.\n *\n * Files are organized by date (`2026-03-14.jsonl`) with optional size-based\n * rotation and automatic cleanup of old files.\n *\n * @example\n * ```ts\n * // Default: writes to .evlog/logs/\n * nitroApp.hooks.hook('evlog:drain', createFsDrain())\n *\n * // With options\n * nitroApp.hooks.hook('evlog:drain', createFsDrain({\n * dir: '.evlog/logs',\n * maxFiles: 7,\n * pretty: true,\n * }))\n * ```\n */\nexport function createFsDrain(overrides?: Partial<FsConfig>) {\n return defineDrain<FsConfig>({\n name: 'fs',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<FsConfig>('fs', FS_FIELDS, overrides)\n return {\n dir: resolved.dir ?? '.evlog/logs',\n pretty: resolved.pretty ?? false,\n maxFiles: resolved.maxFiles,\n maxSizePerFile: resolved.maxSizePerFile,\n }\n },\n send: writeBatchToFs,\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fs.mjs","names":[],"sources":["../../src/adapters/fs.ts"],"sourcesContent":["import { createReadStream } from 'node:fs'\nimport { appendFile, mkdir, open, readdir, stat, unlink, writeFile } from 'node:fs/promises'\nimport { join, sep } from 'node:path'\nimport { createInterface } from 'node:readline'\nimport type { LogLevel, WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineDrain } from '../shared/drain'\n\nexport interface FsConfig {\n /** Directory for log files. Default: `.evlog/logs` */\n dir: string\n /** Max number of log files to keep (auto-deletes oldest when exceeded) */\n maxFiles?: number\n /** Max bytes per file before rotating to a new suffixed file */\n maxSizePerFile?: number\n /** Pretty-print JSON instead of compact NDJSON */\n pretty: boolean\n}\n\nconst FS_FIELDS: ConfigField<FsConfig>[] = [\n { key: 'dir', env: ['NUXT_EVLOG_FS_DIR', 'EVLOG_FS_DIR'] },\n { key: 'maxFiles' },\n { key: 'maxSizePerFile' },\n { key: 'pretty' },\n]\n\nconst gitignoreWritten = new Set<string>()\n\nasync function ensureGitignore(dir: string): Promise<void> {\n const normalized = dir.replace(/[\\\\/]/g, sep)\n const segments = normalized.split(sep)\n const evlogIndex = segments.findIndex(s => s === '.evlog')\n const targetDir = evlogIndex !== -1 ? segments.slice(0, evlogIndex + 1).join(sep) : dir\n\n if (gitignoreWritten.has(targetDir)) return\n\n const gitignorePath = join(targetDir, '.gitignore')\n try {\n await stat(gitignorePath)\n } catch {\n await writeFile(gitignorePath, '*\\n', 'utf-8')\n }\n gitignoreWritten.add(targetDir)\n}\n\nfunction getDateString(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nasync function resolveFilePath(dir: string, maxSizePerFile?: number): Promise<string> {\n const date = getDateString()\n const basePath = join(dir, `${date}.jsonl`)\n\n if (!maxSizePerFile) return basePath\n\n try {\n const stats = await stat(basePath)\n if (stats.size < maxSizePerFile) return basePath\n } catch {\n return basePath\n }\n\n for (let i = 1; i < 1000; i++) {\n const rotatedPath = join(dir, `${date}.${i}.jsonl`)\n try {\n const stats = await stat(rotatedPath)\n if (stats.size < maxSizePerFile) return rotatedPath\n } catch {\n return rotatedPath\n }\n }\n\n return join(dir, `${date}.999.jsonl`)\n}\n\nfunction parseLogFilename(filename: string): { date: string; index: number } {\n const match = filename.match(/^(\\d{4}-\\d{2}-\\d{2})(?:\\.(\\d+))?\\.jsonl$/)\n if (!match) return { date: '', index: 0 }\n return { date: match[1], index: match[2] ? Number.parseInt(match[2], 10) : 0 }\n}\n\nasync function cleanupOldFiles(dir: string, maxFiles: number): Promise<void> {\n const files = await readdir(dir)\n const jsonlFiles = files.filter(f => f.endsWith('.jsonl')).sort((a, b) => {\n const pa = parseLogFilename(a)\n const pb = parseLogFilename(b)\n return pa.date.localeCompare(pb.date) || pa.index - pb.index\n })\n\n if (jsonlFiles.length <= maxFiles) return\n\n const toDelete = jsonlFiles.slice(0, jsonlFiles.length - maxFiles)\n await Promise.allSettled(toDelete.map(f => unlink(join(dir, f))))\n}\n\nexport async function writeToFs(event: WideEvent, config: FsConfig): Promise<void> {\n await writeBatchToFs([event], config)\n}\n\nexport async function writeBatchToFs(events: WideEvent[], config: FsConfig): Promise<void> {\n if (events.length === 0) return\n\n await mkdir(config.dir, { recursive: true })\n await ensureGitignore(config.dir)\n\n const filePath = await resolveFilePath(config.dir, config.maxSizePerFile)\n const lines = `${events\n .map(e => config.pretty ? JSON.stringify(e, null, 2) : JSON.stringify(e))\n .join('\\n') }\\n`\n\n await appendFile(filePath, lines, 'utf-8')\n\n if (config.maxFiles) {\n await cleanupOldFiles(config.dir, config.maxFiles)\n }\n}\n\n/**\n * Create a drain function that writes logs to the local file system as NDJSON.\n *\n * Files are organized by date (`2026-03-14.jsonl`) with optional size-based\n * rotation and automatic cleanup of old files.\n *\n * @example\n * ```ts\n * // Default: writes to .evlog/logs/\n * nitroApp.hooks.hook('evlog:drain', createFsDrain())\n *\n * // With options\n * nitroApp.hooks.hook('evlog:drain', createFsDrain({\n * dir: '.evlog/logs',\n * maxFiles: 7,\n * pretty: true,\n * }))\n * ```\n */\nexport function createFsDrain(overrides?: Partial<FsConfig>) {\n return defineDrain<FsConfig>({\n name: 'fs',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<FsConfig>('fs', FS_FIELDS, overrides)\n return {\n dir: resolved.dir ?? '.evlog/logs',\n pretty: resolved.pretty ?? false,\n maxFiles: resolved.maxFiles,\n maxSizePerFile: resolved.maxSizePerFile,\n }\n },\n send: writeBatchToFs,\n })\n}\n\n/** Options accepted by {@link readFsLogs}. */\nexport interface ReadFsLogsOptions {\n /** Directory to read from. Default: `.evlog/logs` */\n dir?: string\n /** Only yield events with `event.timestamp >= since`. */\n since?: Date | string\n /** Only yield events with `event.timestamp <= until`. */\n until?: Date | string\n /** Filter by event level. */\n level?: LogLevel | LogLevel[]\n /** Custom predicate — return `false` to skip the event. */\n filter?: (event: WideEvent) => boolean\n}\n\n/** Options accepted by {@link tailFsLogs}. */\nexport interface TailFsLogsOptions extends ReadFsLogsOptions {\n /**\n * Polling interval (ms) used to detect new bytes / new files.\n * @default 500\n */\n pollIntervalMs?: number\n /**\n * Skip existing events and only yield events appended after the tailer\n * starts. Useful for \"live tail\" UX.\n * @default false\n */\n fromEnd?: boolean\n /** Stop tailing when this signal aborts. */\n signal?: AbortSignal\n}\n\ninterface ParsedFilename {\n date: string\n index: number\n}\n\nfunction isLogFilename(filename: string): boolean {\n return /^\\d{4}-\\d{2}-\\d{2}(\\.\\d+)?\\.jsonl$/.test(filename)\n}\n\nfunction compareLogFiles(a: string, b: string): number {\n const pa = parseLogFilename(a)\n const pb = parseLogFilename(b)\n return pa.date.localeCompare(pb.date) || pa.index - pb.index\n}\n\nfunction normalizeTimestamp(value: Date | string | undefined): number | undefined {\n if (!value) return undefined\n const date = value instanceof Date ? value : new Date(value)\n const ts = date.getTime()\n return Number.isNaN(ts) ? undefined : ts\n}\n\nfunction buildFilter(options: ReadFsLogsOptions): (event: WideEvent) => boolean {\n const since = normalizeTimestamp(options.since)\n const until = normalizeTimestamp(options.until)\n const levels = options.level\n ? new Set<LogLevel>(Array.isArray(options.level) ? options.level : [options.level])\n : undefined\n const custom = options.filter\n\n return (event: WideEvent) => {\n if (levels && !levels.has(event.level)) return false\n if (since !== undefined || until !== undefined) {\n const ts = typeof event.timestamp === 'string' ? Date.parse(event.timestamp) : Number.NaN\n if (Number.isNaN(ts)) return false\n if (since !== undefined && ts < since) return false\n if (until !== undefined && ts > until) return false\n }\n if (custom && !custom(event)) return false\n return true\n }\n}\n\nasync function listLogFiles(dir: string): Promise<string[]> {\n let files: string[]\n try {\n files = await readdir(dir)\n } catch {\n return []\n }\n return files.filter(isLogFilename).sort(compareLogFiles)\n}\n\nfunction fileDateMs(filename: string): number {\n const { date } = parseLogFilename(filename)\n return date ? Date.parse(`${date}T00:00:00.000Z`) : Number.NaN\n}\n\nfunction fileWithinRange(filename: string, since?: number, until?: number): boolean {\n if (since === undefined && until === undefined) return true\n const dayStart = fileDateMs(filename)\n if (Number.isNaN(dayStart)) return true\n const dayEnd = dayStart + 24 * 60 * 60 * 1000 - 1\n if (since !== undefined && dayEnd < since) return false\n if (until !== undefined && dayStart > until) return false\n return true\n}\n\nasync function* iterateFile(filePath: string): AsyncGenerator<WideEvent> {\n const stream = createReadStream(filePath, { encoding: 'utf-8' })\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n try {\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n try {\n yield JSON.parse(trimmed) as WideEvent\n } catch {\n // Skip malformed lines (partial writes, manual edits) silently.\n }\n }\n } finally {\n rl.close()\n stream.destroy()\n }\n}\n\n/**\n * Read past events from the local file system drain (NDJSON). Files are\n * iterated in chronological order; events are yielded as they appear in\n * each file.\n *\n * @example\n * ```ts\n * import { readFsLogs } from 'evlog/fs'\n *\n * for await (const event of readFsLogs({ since: '2026-01-01', level: 'error' })) {\n * console.log(event)\n * }\n * ```\n */\nexport async function* readFsLogs(options: ReadFsLogsOptions = {}): AsyncGenerator<WideEvent> {\n const dir = options.dir ?? '.evlog/logs'\n const sinceMs = normalizeTimestamp(options.since)\n const untilMs = normalizeTimestamp(options.until)\n const predicate = buildFilter(options)\n\n const files = await listLogFiles(dir)\n for (const filename of files) {\n if (!fileWithinRange(filename, sinceMs, untilMs)) continue\n for await (const event of iterateFile(join(dir, filename))) {\n if (predicate(event)) yield event\n }\n }\n}\n\nasync function safeStatSize(filePath: string): Promise<number> {\n try {\n const s = await stat(filePath)\n return s.size\n } catch {\n return 0\n }\n}\n\nasync function readAppendedLines(\n filePath: string,\n fromOffset: number,\n carry: string,\n): Promise<{ events: string[]; offset: number; carry: string }> {\n const size = await safeStatSize(filePath)\n if (size <= fromOffset) return { events: [], offset: fromOffset, carry }\n\n const handle = await open(filePath, 'r')\n try {\n const length = size - fromOffset\n const buf = Buffer.alloc(length)\n await handle.read(buf, 0, length, fromOffset)\n const chunk = carry + buf.toString('utf-8')\n const newlineIdx = chunk.lastIndexOf('\\n')\n if (newlineIdx === -1) {\n return { events: [], offset: size, carry: chunk }\n }\n const complete = chunk.slice(0, newlineIdx)\n const remainder = chunk.slice(newlineIdx + 1)\n const lines = complete.split('\\n').map(l => l.trim()).filter(Boolean)\n return { events: lines, offset: size, carry: remainder }\n } finally {\n await handle.close()\n }\n}\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n if (signal) signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n if (signal) {\n if (signal.aborted) {\n clearTimeout(timer)\n resolve()\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n }\n })\n}\n\n/**\n * Follow the local file system drain in real time. Yields existing events\n * (unless `fromEnd: true`) then keeps yielding new events as they are\n * appended. Automatically picks up newly created daily files.\n *\n * @example\n * ```ts\n * import { tailFsLogs } from 'evlog/fs'\n *\n * const ac = new AbortController()\n * setTimeout(() => ac.abort(), 60_000)\n *\n * for await (const event of tailFsLogs({ signal: ac.signal })) {\n * console.log('live:', event.action ?? event.message)\n * }\n * ```\n */\nexport async function* tailFsLogs(options: TailFsLogsOptions = {}): AsyncGenerator<WideEvent> {\n const dir = options.dir ?? '.evlog/logs'\n const interval = Math.max(50, options.pollIntervalMs ?? 500)\n const { signal } = options\n const predicate = buildFilter(options)\n\n const offsets = new Map<string, number>()\n const carries = new Map<string, string>()\n\n if (options.fromEnd) {\n const files = await listLogFiles(dir)\n for (const filename of files) {\n offsets.set(filename, await safeStatSize(join(dir, filename)))\n carries.set(filename, '')\n }\n } else {\n for await (const event of readFsLogs(options)) {\n if (signal?.aborted) return\n yield event\n }\n const files = await listLogFiles(dir)\n for (const filename of files) {\n if (!offsets.has(filename)) {\n offsets.set(filename, await safeStatSize(join(dir, filename)))\n carries.set(filename, '')\n }\n }\n }\n\n while (true) {\n if (signal?.aborted) return\n await delay(interval, signal)\n if (signal?.aborted) return\n\n const files = await listLogFiles(dir)\n\n for (const filename of files) {\n if (!offsets.has(filename)) {\n offsets.set(filename, 0)\n carries.set(filename, '')\n }\n const filePath = join(dir, filename)\n const fromOffset = offsets.get(filename)!\n const carry = carries.get(filename) ?? ''\n const { events, offset, carry: newCarry } = await readAppendedLines(filePath, fromOffset, carry)\n offsets.set(filename, offset)\n carries.set(filename, newCarry)\n\n for (const line of events) {\n if (signal?.aborted) return\n try {\n const event = JSON.parse(line) as WideEvent\n if (predicate(event)) yield event\n } catch {\n // Skip malformed lines.\n }\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { F as DrainContext, it as WideEvent } from "../audit-X1uUukm3.mjs";
1
+ import { I as DrainContext, ct as WideEvent } from "../audit-DVdkntSO.mjs";
2
2
  import { OTLPConfig } from "./otlp.mjs";
3
3
 
4
4
  //#region src/adapters/hyperdx.d.ts
@@ -1,4 +1,4 @@
1
- import { a as resolveAdapterConfig, t as defineDrain } from "../drain-ByWUeOQC.mjs";
1
+ import { i as resolveAdapterConfig, t as defineDrain } from "../drain-7n3K6kPe.mjs";
2
2
  import { sendBatchToOTLP } from "./otlp.mjs";
3
3
  //#region src/adapters/hyperdx.ts
4
4
  /**
@@ -0,0 +1,116 @@
1
+ import { I as DrainContext, K as LogLevel, ct as WideEvent } from "../audit-DVdkntSO.mjs";
2
+ //#region src/adapters/memory.d.ts
3
+ /**
4
+ * Configuration for the in-memory drain.
5
+ */
6
+ interface MemoryConfig {
7
+ /** Named store key. Multiple drains sharing the same key share the same buffer. Default: `'default'` */
8
+ store: string;
9
+ /** Maximum number of events to keep in the ring buffer. Oldest events are discarded when the limit is exceeded. Default: `1000` */
10
+ maxEvents: number;
11
+ }
12
+ /**
13
+ * Write events directly into the named store. Exported for direct use and
14
+ * easier testing without going through the drain pipeline.
15
+ */
16
+ declare function writeToMemory(events: WideEvent[], config: MemoryConfig): void;
17
+ /**
18
+ * Create a drain that stores wide events in an in-memory ring buffer.
19
+ *
20
+ * Works in **any** runtime — including Cloudflare Workers (workerd) — where
21
+ * the filesystem (`evlog/fs`) is not available. Pair it with a dev-only HTTP
22
+ * endpoint to let agents retrieve the buffer over HTTP.
23
+ *
24
+ * Configuration priority (highest to lowest):
25
+ * 1. Overrides passed to `createMemoryDrain()`
26
+ * 2. `runtimeConfig.evlog.memory` / `runtimeConfig.memory` (Nitro)
27
+ * 3. Environment variables: `NUXT_EVLOG_MEMORY_STORE`, `EVLOG_MEMORY_STORE`,
28
+ * `NUXT_EVLOG_MEMORY_MAX_EVENTS`, `EVLOG_MEMORY_MAX_EVENTS`
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // Hono + Cloudflare Workers
33
+ * import { createMemoryDrain, readMemoryLogs } from 'evlog/memory'
34
+ *
35
+ * app.use(evlog({ drain: createMemoryDrain() }))
36
+ *
37
+ * // Dev-only endpoint for agent retrieval
38
+ * if (env.NODE_ENV === 'development') {
39
+ * app.get('/_evlog/logs', (c) => c.json(readMemoryLogs()))
40
+ * }
41
+ * ```
42
+ */
43
+ declare function createMemoryDrain(overrides?: Partial<MemoryConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void>;
44
+ /** Options accepted by {@link readMemoryLogs}. */
45
+ interface ReadMemoryLogsOptions {
46
+ /** Named store to read from. Default: `'default'` */
47
+ store?: string;
48
+ /** Only include events with `timestamp >= since`. */
49
+ since?: Date | string;
50
+ /** Only include events with `timestamp <= until`. */
51
+ until?: Date | string;
52
+ /** Filter by log level. */
53
+ level?: LogLevel | LogLevel[];
54
+ /** Custom predicate — return `false` to skip the event. */
55
+ filter?: (event: WideEvent) => boolean;
56
+ /** Return at most this many of the most-recent matching events. */
57
+ limit?: number;
58
+ }
59
+ /**
60
+ * Read events from the named in-memory store. Returns a snapshot of the
61
+ * buffer with optional filtering, ordered oldest-first.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * import { readMemoryLogs } from 'evlog/memory'
66
+ *
67
+ * // All events
68
+ * const events = readMemoryLogs()
69
+ *
70
+ * // Errors in the last hour
71
+ * const errors = readMemoryLogs({
72
+ * level: 'error',
73
+ * since: new Date(Date.now() - 60 * 60 * 1000),
74
+ * })
75
+ *
76
+ * // Expose as a JSON endpoint
77
+ * app.get('/_evlog/logs', (c) => c.json(readMemoryLogs({ limit: 200 })))
78
+ * ```
79
+ */
80
+ declare function readMemoryLogs(options?: ReadMemoryLogsOptions): WideEvent[];
81
+ /**
82
+ * Clear all events from a named store (or `'default'`).
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * clearMemoryLogs() // clears the default store
87
+ * clearMemoryLogs('my-store') // clears a named store
88
+ * ```
89
+ */
90
+ declare function clearMemoryLogs(store?: string): void;
91
+ /**
92
+ * Parse a flat query-string object (e.g. from `c.req.query()` in Hono, or
93
+ * `req.query` in Express) into {@link ReadMemoryLogsOptions} with proper type
94
+ * coercion.
95
+ *
96
+ * This lets agents discover and pass filter parameters through HTTP query
97
+ * strings directly:
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * // Hono — zero glue
102
+ * app.get('/_evlog/logs', (c) =>
103
+ * c.json(readMemoryLogs(parseReadMemoryLogsQuery(c.req.query()))))
104
+ *
105
+ * // Express
106
+ * app.get('/_evlog/logs', (req, res) =>
107
+ * res.json(readMemoryLogs(parseReadMemoryLogsQuery(req.query as Record<string, string>))))
108
+ * ```
109
+ *
110
+ * Supported query params: `store`, `since`, `until`, `level` (comma-separated),
111
+ * `limit`. The `filter` predicate cannot be expressed as a query param.
112
+ */
113
+ declare function parseReadMemoryLogsQuery(query: Record<string, string | string[] | undefined>): ReadMemoryLogsOptions;
114
+ //#endregion
115
+ export { MemoryConfig, ReadMemoryLogsOptions, clearMemoryLogs, createMemoryDrain, parseReadMemoryLogsQuery, readMemoryLogs, writeToMemory };
116
+ //# sourceMappingURL=memory.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.mts","names":[],"sources":["../../src/adapters/memory.ts"],"mappings":";;;;;UAQiB,YAAA;EAAY;EAE3B,KAAA;EAAA;EAEA,SAAA;AAAA;;;;;iBA2Cc,aAAA,CAAc,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,YAAA;;;;;AAmC3D;;;;;;;;;;;;;;;;;;;AAYA;;;iBAZgB,iBAAA,CAAkB,SAAA,GAAY,OAAA,CAAQ,YAAA,KAAa,GAAA,EAAd,YAAA,GAAc,YAAA,OAAA,OAAA;;UAYlD,qBAAA;EAQI;EANnB,KAAA;EAQ0B;EAN1B,KAAA,GAAQ,IAAA;EAFR;EAIA,KAAA,GAAQ,IAAA;EAFA;EAIR,KAAA,GAAQ,QAAA,GAAW,QAAA;EAFX;EAIR,MAAA,IAAU,KAAA,EAAO,SAAA;EAFT;EAIR,KAAA;AAAA;;;;;;AA+BF;;;;;;;;;AAuCA;;;;;AA6BA;;iBApEgB,cAAA,CAAe,OAAA,GAAS,qBAAA,GAA6B,SAAA;;;;;;;;;;iBAuCrD,eAAA,CAAgB,KAAA;;;;;;;;;;;;;;;;;;;;;;;iBA6BhB,wBAAA,CACd,KAAA,EAAO,MAAA,0CACN,qBAAA"}
@@ -0,0 +1,191 @@
1
+ import { i as resolveAdapterConfig, t as defineDrain } from "../drain-7n3K6kPe.mjs";
2
+ //#region src/adapters/memory.ts
3
+ const DEFAULT_STORE = "default";
4
+ const DEFAULT_MAX_EVENTS = 1e3;
5
+ const MEMORY_FIELDS = [{
6
+ key: "store",
7
+ env: ["NUXT_EVLOG_MEMORY_STORE", "EVLOG_MEMORY_STORE"]
8
+ }, {
9
+ key: "maxEvents",
10
+ env: ["NUXT_EVLOG_MEMORY_MAX_EVENTS", "EVLOG_MEMORY_MAX_EVENTS"]
11
+ }];
12
+ const stores = /* @__PURE__ */ new Map();
13
+ function getOrCreateStore(name) {
14
+ if (!stores.has(name)) stores.set(name, []);
15
+ return stores.get(name);
16
+ }
17
+ function parseMaxEvents(value) {
18
+ if (typeof value === "number" && Number.isFinite(value)) {
19
+ const n = Math.floor(value);
20
+ return n > 0 ? n : void 0;
21
+ }
22
+ if (typeof value === "string" && value) {
23
+ const parsed = Number.parseFloat(value);
24
+ if (!Number.isFinite(parsed)) return void 0;
25
+ const n = Math.floor(parsed);
26
+ return n > 0 ? n : void 0;
27
+ }
28
+ }
29
+ function resolveMemoryConfig(overrides) {
30
+ return {
31
+ store: overrides?.store ?? DEFAULT_STORE,
32
+ maxEvents: parseMaxEvents(overrides?.maxEvents) ?? DEFAULT_MAX_EVENTS
33
+ };
34
+ }
35
+ /**
36
+ * Write events directly into the named store. Exported for direct use and
37
+ * easier testing without going through the drain pipeline.
38
+ */
39
+ function writeToMemory(events, config) {
40
+ if (events.length === 0) return;
41
+ const store = getOrCreateStore(config.store);
42
+ store.push(...events);
43
+ if (store.length > config.maxEvents) store.splice(0, store.length - config.maxEvents);
44
+ }
45
+ /**
46
+ * Create a drain that stores wide events in an in-memory ring buffer.
47
+ *
48
+ * Works in **any** runtime — including Cloudflare Workers (workerd) — where
49
+ * the filesystem (`evlog/fs`) is not available. Pair it with a dev-only HTTP
50
+ * endpoint to let agents retrieve the buffer over HTTP.
51
+ *
52
+ * Configuration priority (highest to lowest):
53
+ * 1. Overrides passed to `createMemoryDrain()`
54
+ * 2. `runtimeConfig.evlog.memory` / `runtimeConfig.memory` (Nitro)
55
+ * 3. Environment variables: `NUXT_EVLOG_MEMORY_STORE`, `EVLOG_MEMORY_STORE`,
56
+ * `NUXT_EVLOG_MEMORY_MAX_EVENTS`, `EVLOG_MEMORY_MAX_EVENTS`
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * // Hono + Cloudflare Workers
61
+ * import { createMemoryDrain, readMemoryLogs } from 'evlog/memory'
62
+ *
63
+ * app.use(evlog({ drain: createMemoryDrain() }))
64
+ *
65
+ * // Dev-only endpoint for agent retrieval
66
+ * if (env.NODE_ENV === 'development') {
67
+ * app.get('/_evlog/logs', (c) => c.json(readMemoryLogs()))
68
+ * }
69
+ * ```
70
+ */
71
+ function createMemoryDrain(overrides) {
72
+ return defineDrain({
73
+ name: "memory",
74
+ resolve: async () => {
75
+ return resolveMemoryConfig(await resolveAdapterConfig("memory", MEMORY_FIELDS, overrides));
76
+ },
77
+ send: (events, cfg) => Promise.resolve(writeToMemory(events, cfg))
78
+ });
79
+ }
80
+ function normalizeTimestamp(value) {
81
+ if (!value) return void 0;
82
+ const ts = (value instanceof Date ? value : new Date(value)).getTime();
83
+ return Number.isNaN(ts) ? void 0 : ts;
84
+ }
85
+ /**
86
+ * Read events from the named in-memory store. Returns a snapshot of the
87
+ * buffer with optional filtering, ordered oldest-first.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * import { readMemoryLogs } from 'evlog/memory'
92
+ *
93
+ * // All events
94
+ * const events = readMemoryLogs()
95
+ *
96
+ * // Errors in the last hour
97
+ * const errors = readMemoryLogs({
98
+ * level: 'error',
99
+ * since: new Date(Date.now() - 60 * 60 * 1000),
100
+ * })
101
+ *
102
+ * // Expose as a JSON endpoint
103
+ * app.get('/_evlog/logs', (c) => c.json(readMemoryLogs({ limit: 200 })))
104
+ * ```
105
+ */
106
+ function readMemoryLogs(options = {}) {
107
+ const storeName = options.store ?? DEFAULT_STORE;
108
+ const events = [...stores.get(storeName) ?? []];
109
+ const sinceMs = normalizeTimestamp(options.since);
110
+ const untilMs = normalizeTimestamp(options.until);
111
+ const levels = options.level ? new Set(Array.isArray(options.level) ? options.level : [options.level]) : void 0;
112
+ const custom = options.filter;
113
+ const filtered = events.filter((event) => {
114
+ if (levels && !levels.has(event.level)) return false;
115
+ if (sinceMs !== void 0 || untilMs !== void 0) {
116
+ const ts = typeof event.timestamp === "string" ? Date.parse(event.timestamp) : NaN;
117
+ if (Number.isNaN(ts)) return false;
118
+ if (sinceMs !== void 0 && ts < sinceMs) return false;
119
+ if (untilMs !== void 0 && ts > untilMs) return false;
120
+ }
121
+ if (custom && !custom(event)) return false;
122
+ return true;
123
+ });
124
+ if (options.limit !== void 0) {
125
+ if (options.limit <= 0) return [];
126
+ if (filtered.length > options.limit) return filtered.slice(-options.limit);
127
+ }
128
+ return filtered;
129
+ }
130
+ /**
131
+ * Clear all events from a named store (or `'default'`).
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * clearMemoryLogs() // clears the default store
136
+ * clearMemoryLogs('my-store') // clears a named store
137
+ * ```
138
+ */
139
+ function clearMemoryLogs(store = DEFAULT_STORE) {
140
+ const s = stores.get(store);
141
+ if (s) s.length = 0;
142
+ }
143
+ const VALID_LEVELS = new Set([
144
+ "info",
145
+ "error",
146
+ "warn",
147
+ "debug"
148
+ ]);
149
+ /**
150
+ * Parse a flat query-string object (e.g. from `c.req.query()` in Hono, or
151
+ * `req.query` in Express) into {@link ReadMemoryLogsOptions} with proper type
152
+ * coercion.
153
+ *
154
+ * This lets agents discover and pass filter parameters through HTTP query
155
+ * strings directly:
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * // Hono — zero glue
160
+ * app.get('/_evlog/logs', (c) =>
161
+ * c.json(readMemoryLogs(parseReadMemoryLogsQuery(c.req.query()))))
162
+ *
163
+ * // Express
164
+ * app.get('/_evlog/logs', (req, res) =>
165
+ * res.json(readMemoryLogs(parseReadMemoryLogsQuery(req.query as Record<string, string>))))
166
+ * ```
167
+ *
168
+ * Supported query params: `store`, `since`, `until`, `level` (comma-separated),
169
+ * `limit`. The `filter` predicate cannot be expressed as a query param.
170
+ */
171
+ function parseReadMemoryLogsQuery(query) {
172
+ const opts = {};
173
+ const { store, since, until, level, limit } = query;
174
+ if (typeof store === "string" && store) opts.store = store;
175
+ if (typeof since === "string" && since) opts.since = since;
176
+ if (typeof until === "string" && until) opts.until = until;
177
+ if (level !== void 0) {
178
+ const levels = (Array.isArray(level) ? level : level.split(",")).map((l) => l.trim()).filter((l) => VALID_LEVELS.has(l));
179
+ if (levels.length === 1) [opts.level] = levels;
180
+ else if (levels.length > 1) opts.level = levels;
181
+ }
182
+ if (typeof limit === "string") {
183
+ const n = Number.parseInt(limit, 10);
184
+ if (!Number.isNaN(n)) opts.limit = n;
185
+ }
186
+ return opts;
187
+ }
188
+ //#endregion
189
+ export { clearMemoryLogs, createMemoryDrain, parseReadMemoryLogsQuery, readMemoryLogs, writeToMemory };
190
+
191
+ //# sourceMappingURL=memory.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.mjs","names":[],"sources":["../../src/adapters/memory.ts"],"sourcesContent":["import type { LogLevel, WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineDrain } from '../shared/drain'\n\n/**\n * Configuration for the in-memory drain.\n */\nexport interface MemoryConfig {\n /** Named store key. Multiple drains sharing the same key share the same buffer. Default: `'default'` */\n store: string\n /** Maximum number of events to keep in the ring buffer. Oldest events are discarded when the limit is exceeded. Default: `1000` */\n maxEvents: number\n}\n\nconst DEFAULT_STORE = 'default'\nconst DEFAULT_MAX_EVENTS = 1000\n\nconst MEMORY_FIELDS: ConfigField<Partial<MemoryConfig>>[] = [\n { key: 'store', env: ['NUXT_EVLOG_MEMORY_STORE', 'EVLOG_MEMORY_STORE'] },\n { key: 'maxEvents', env: ['NUXT_EVLOG_MEMORY_MAX_EVENTS', 'EVLOG_MEMORY_MAX_EVENTS'] },\n]\n\nconst stores = new Map<string, WideEvent[]>()\n\nfunction getOrCreateStore(name: string): WideEvent[] {\n if (!stores.has(name)) stores.set(name, [])\n return stores.get(name)!\n}\n\nfunction parseMaxEvents(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n const n = Math.floor(value)\n return n > 0 ? n : undefined\n }\n if (typeof value === 'string' && value) {\n const parsed = Number.parseFloat(value)\n if (!Number.isFinite(parsed)) return undefined\n const n = Math.floor(parsed)\n return n > 0 ? n : undefined\n }\n return undefined\n}\n\nfunction resolveMemoryConfig(overrides?: Partial<MemoryConfig>): MemoryConfig {\n return {\n store: overrides?.store ?? DEFAULT_STORE,\n maxEvents: parseMaxEvents(overrides?.maxEvents) ?? DEFAULT_MAX_EVENTS,\n }\n}\n\n/**\n * Write events directly into the named store. Exported for direct use and\n * easier testing without going through the drain pipeline.\n */\nexport function writeToMemory(events: WideEvent[], config: MemoryConfig): void {\n if (events.length === 0) return\n const store = getOrCreateStore(config.store)\n store.push(...events)\n if (store.length > config.maxEvents) {\n store.splice(0, store.length - config.maxEvents)\n }\n}\n\n/**\n * Create a drain that stores wide events in an in-memory ring buffer.\n *\n * Works in **any** runtime — including Cloudflare Workers (workerd) — where\n * the filesystem (`evlog/fs`) is not available. Pair it with a dev-only HTTP\n * endpoint to let agents retrieve the buffer over HTTP.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to `createMemoryDrain()`\n * 2. `runtimeConfig.evlog.memory` / `runtimeConfig.memory` (Nitro)\n * 3. Environment variables: `NUXT_EVLOG_MEMORY_STORE`, `EVLOG_MEMORY_STORE`,\n * `NUXT_EVLOG_MEMORY_MAX_EVENTS`, `EVLOG_MEMORY_MAX_EVENTS`\n *\n * @example\n * ```ts\n * // Hono + Cloudflare Workers\n * import { createMemoryDrain, readMemoryLogs } from 'evlog/memory'\n *\n * app.use(evlog({ drain: createMemoryDrain() }))\n *\n * // Dev-only endpoint for agent retrieval\n * if (env.NODE_ENV === 'development') {\n * app.get('/_evlog/logs', (c) => c.json(readMemoryLogs()))\n * }\n * ```\n */\nexport function createMemoryDrain(overrides?: Partial<MemoryConfig>) {\n return defineDrain<MemoryConfig>({\n name: 'memory',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<Partial<MemoryConfig>>('memory', MEMORY_FIELDS, overrides)\n return resolveMemoryConfig(resolved)\n },\n send: (events, cfg) => Promise.resolve(writeToMemory(events, cfg)),\n })\n}\n\n/** Options accepted by {@link readMemoryLogs}. */\nexport interface ReadMemoryLogsOptions {\n /** Named store to read from. Default: `'default'` */\n store?: string\n /** Only include events with `timestamp >= since`. */\n since?: Date | string\n /** Only include events with `timestamp <= until`. */\n until?: Date | string\n /** Filter by log level. */\n level?: LogLevel | LogLevel[]\n /** Custom predicate — return `false` to skip the event. */\n filter?: (event: WideEvent) => boolean\n /** Return at most this many of the most-recent matching events. */\n limit?: number\n}\n\nfunction normalizeTimestamp(value: Date | string | undefined): number | undefined {\n if (!value) return undefined\n const date = value instanceof Date ? value : new Date(value)\n const ts = date.getTime()\n return Number.isNaN(ts) ? undefined : ts\n}\n\n/**\n * Read events from the named in-memory store. Returns a snapshot of the\n * buffer with optional filtering, ordered oldest-first.\n *\n * @example\n * ```ts\n * import { readMemoryLogs } from 'evlog/memory'\n *\n * // All events\n * const events = readMemoryLogs()\n *\n * // Errors in the last hour\n * const errors = readMemoryLogs({\n * level: 'error',\n * since: new Date(Date.now() - 60 * 60 * 1000),\n * })\n *\n * // Expose as a JSON endpoint\n * app.get('/_evlog/logs', (c) => c.json(readMemoryLogs({ limit: 200 })))\n * ```\n */\nexport function readMemoryLogs(options: ReadMemoryLogsOptions = {}): WideEvent[] {\n const storeName = options.store ?? DEFAULT_STORE\n const events = [...(stores.get(storeName) ?? [])]\n\n const sinceMs = normalizeTimestamp(options.since)\n const untilMs = normalizeTimestamp(options.until)\n const levels = options.level\n ? new Set<LogLevel>(Array.isArray(options.level) ? options.level : [options.level])\n : undefined\n const custom = options.filter\n\n const filtered = events.filter((event) => {\n if (levels && !levels.has(event.level)) return false\n if (sinceMs !== undefined || untilMs !== undefined) {\n const ts = typeof event.timestamp === 'string' ? Date.parse(event.timestamp) : Number.NaN\n if (Number.isNaN(ts)) return false\n if (sinceMs !== undefined && ts < sinceMs) return false\n if (untilMs !== undefined && ts > untilMs) return false\n }\n if (custom && !custom(event)) return false\n return true\n })\n\n if (options.limit !== undefined) {\n if (options.limit <= 0) return []\n if (filtered.length > options.limit) return filtered.slice(-options.limit)\n }\n return filtered\n}\n\n/**\n * Clear all events from a named store (or `'default'`).\n *\n * @example\n * ```ts\n * clearMemoryLogs() // clears the default store\n * clearMemoryLogs('my-store') // clears a named store\n * ```\n */\nexport function clearMemoryLogs(store = DEFAULT_STORE): void {\n const s = stores.get(store)\n if (s) s.length = 0\n}\n\nconst VALID_LEVELS = new Set<LogLevel>(['info', 'error', 'warn', 'debug'])\n\n/**\n * Parse a flat query-string object (e.g. from `c.req.query()` in Hono, or\n * `req.query` in Express) into {@link ReadMemoryLogsOptions} with proper type\n * coercion.\n *\n * This lets agents discover and pass filter parameters through HTTP query\n * strings directly:\n *\n * @example\n * ```ts\n * // Hono — zero glue\n * app.get('/_evlog/logs', (c) =>\n * c.json(readMemoryLogs(parseReadMemoryLogsQuery(c.req.query()))))\n *\n * // Express\n * app.get('/_evlog/logs', (req, res) =>\n * res.json(readMemoryLogs(parseReadMemoryLogsQuery(req.query as Record<string, string>))))\n * ```\n *\n * Supported query params: `store`, `since`, `until`, `level` (comma-separated),\n * `limit`. The `filter` predicate cannot be expressed as a query param.\n */\nexport function parseReadMemoryLogsQuery(\n query: Record<string, string | string[] | undefined>,\n): ReadMemoryLogsOptions {\n const opts: ReadMemoryLogsOptions = {}\n const { store, since, until, level, limit } = query\n\n if (typeof store === 'string' && store) opts.store = store\n if (typeof since === 'string' && since) opts.since = since\n if (typeof until === 'string' && until) opts.until = until\n\n if (level !== undefined) {\n const raw = Array.isArray(level) ? level : level.split(',')\n const levels = raw.map((l) => l.trim()).filter((l): l is LogLevel => VALID_LEVELS.has(l as LogLevel))\n if (levels.length === 1) [opts.level] = levels\n else if (levels.length > 1) opts.level = levels\n }\n\n if (typeof limit === 'string') {\n const n = Number.parseInt(limit, 10)\n if (!Number.isNaN(n)) opts.limit = n\n }\n\n return opts\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { F as DrainContext, it as WideEvent } from "../audit-X1uUukm3.mjs";
1
+ import { I as DrainContext, ct as WideEvent } from "../audit-DVdkntSO.mjs";
2
2
  //#region src/adapters/otlp.d.ts
3
3
  interface OTLPConfig {
4
4
  /** OTLP HTTP endpoint (e.g., http://localhost:4318) */