evlog 2.16.0 → 2.17.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 (153) hide show
  1. package/README.md +7 -7
  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/otlp.d.mts +1 -1
  18. package/dist/adapters/otlp.mjs +6 -4
  19. package/dist/adapters/otlp.mjs.map +1 -1
  20. package/dist/adapters/posthog.d.mts +1 -1
  21. package/dist/adapters/posthog.mjs +4 -2
  22. package/dist/adapters/posthog.mjs.map +1 -1
  23. package/dist/adapters/sentry.d.mts +1 -1
  24. package/dist/adapters/sentry.mjs +5 -3
  25. package/dist/adapters/sentry.mjs.map +1 -1
  26. package/dist/ai/index.d.mts +1 -1
  27. package/dist/{audit-X1uUukm3.d.mts → audit-CC8nfazi.d.mts} +56 -5
  28. package/dist/{audit-X1uUukm3.d.mts.map → audit-CC8nfazi.d.mts.map} +1 -1
  29. package/dist/better-auth/index.d.mts +14 -7
  30. package/dist/better-auth/index.d.mts.map +1 -1
  31. package/dist/better-auth/index.mjs +11 -1
  32. package/dist/better-auth/index.mjs.map +1 -1
  33. package/dist/browser.d.mts +1 -1
  34. package/dist/{define-CuXOqecD.d.mts → define-MSdhzmXn.d.mts} +3 -3
  35. package/dist/{define-CuXOqecD.d.mts.map → define-MSdhzmXn.d.mts.map} +1 -1
  36. package/dist/{dist-BIlS38vi.mjs → dist-H3GIh-KK.mjs} +1 -1
  37. package/dist/{dist-BIlS38vi.mjs.map → dist-H3GIh-KK.mjs.map} +1 -1
  38. package/dist/{drain-ByWUeOQC.mjs → drain-X7_5szSI.mjs} +6 -49
  39. package/dist/drain-X7_5szSI.mjs.map +1 -0
  40. package/dist/elysia/index.d.mts +2 -2
  41. package/dist/elysia/index.mjs +2 -2
  42. package/dist/{enricher-DYTr9I16.d.mts → enricher-DxgML6IC.d.mts} +2 -2
  43. package/dist/{enricher-DYTr9I16.d.mts.map → enricher-DxgML6IC.d.mts.map} +1 -1
  44. package/dist/{enricher-Dy06T17G.mjs → enricher-N0erZS87.mjs} +2 -2
  45. package/dist/{enricher-Dy06T17G.mjs.map → enricher-N0erZS87.mjs.map} +1 -1
  46. package/dist/enrichers.d.mts +2 -2
  47. package/dist/enrichers.mjs +1 -1
  48. package/dist/{error-Cpc7RVz6.d.mts → error-CpbbtyXL.d.mts} +2 -2
  49. package/dist/{error-Cpc7RVz6.d.mts.map → error-CpbbtyXL.d.mts.map} +1 -1
  50. package/dist/error.d.mts +1 -1
  51. package/dist/{errors-prnQ3kES.d.mts → errors-DySW1F9_.d.mts} +2 -2
  52. package/dist/{errors-prnQ3kES.d.mts.map → errors-DySW1F9_.d.mts.map} +1 -1
  53. package/dist/{event-DcHmEm3O.mjs → event-1BMl7o0k.mjs} +1 -1
  54. package/dist/{event-DcHmEm3O.mjs.map → event-1BMl7o0k.mjs.map} +1 -1
  55. package/dist/express/index.d.mts +2 -2
  56. package/dist/express/index.d.mts.map +1 -1
  57. package/dist/express/index.mjs +5 -6
  58. package/dist/express/index.mjs.map +1 -1
  59. package/dist/fastify/index.d.mts +2 -2
  60. package/dist/fastify/index.mjs +2 -2
  61. package/dist/{fork-DPN8aL8O.mjs → fork-8u_zFOJq.mjs} +2 -2
  62. package/dist/{fork-DPN8aL8O.mjs.map → fork-8u_zFOJq.mjs.map} +1 -1
  63. package/dist/hono/index.d.mts +2 -2
  64. package/dist/hono/index.mjs +1 -1
  65. package/dist/http-6umVAKDW.mjs +82 -0
  66. package/dist/http-6umVAKDW.mjs.map +1 -0
  67. package/dist/http.d.mts +1 -1
  68. package/dist/http.mjs +1 -0
  69. package/dist/http.mjs.map +1 -1
  70. package/dist/index-o1_z4phv.d.mts +213 -0
  71. package/dist/index-o1_z4phv.d.mts.map +1 -0
  72. package/dist/index.d.mts +9 -8
  73. package/dist/index.mjs +209 -1
  74. package/dist/index.mjs.map +1 -0
  75. package/dist/{integration-DSZPbI9N.mjs → integration-DTZtjSqh.mjs} +2 -2
  76. package/dist/{integration-DSZPbI9N.mjs.map → integration-DTZtjSqh.mjs.map} +1 -1
  77. package/dist/{logger-U8lgdc9x.d.mts → logger-DntcxxHg.d.mts} +2 -2
  78. package/dist/{logger-U8lgdc9x.d.mts.map → logger-DntcxxHg.d.mts.map} +1 -1
  79. package/dist/logger.d.mts +1 -1
  80. package/dist/{middleware-CAQHJRN1.d.mts → middleware-U-lIAzHg.d.mts} +2 -2
  81. package/dist/{middleware-CAQHJRN1.d.mts.map → middleware-U-lIAzHg.d.mts.map} +1 -1
  82. package/dist/nestjs/index.d.mts +2 -2
  83. package/dist/nestjs/index.d.mts.map +1 -1
  84. package/dist/nestjs/index.mjs +4 -5
  85. package/dist/nestjs/index.mjs.map +1 -1
  86. package/dist/next/client.d.mts +1 -1
  87. package/dist/next/index.d.mts +4 -4
  88. package/dist/next/index.mjs +2 -2
  89. package/dist/next/instrumentation.d.mts +1 -1
  90. package/dist/next/stream.d.mts +29 -0
  91. package/dist/next/stream.d.mts.map +1 -0
  92. package/dist/next/stream.mjs +78 -0
  93. package/dist/next/stream.mjs.map +1 -0
  94. package/dist/nitro/errorHandler.mjs +1 -1
  95. package/dist/nitro/module.d.mts +2 -2
  96. package/dist/nitro/plugin.mjs +11 -2
  97. package/dist/nitro/plugin.mjs.map +1 -1
  98. package/dist/nitro/v3/errorHandler.mjs +2 -2
  99. package/dist/nitro/v3/index.d.mts +2 -2
  100. package/dist/nitro/v3/module.d.mts +1 -1
  101. package/dist/nitro/v3/plugin.mjs +3 -3
  102. package/dist/nitro/v3/useLogger.d.mts +1 -1
  103. package/dist/{nitro-DavLelNz.mjs → nitro-DErMq_Zj.mjs} +1 -1
  104. package/dist/{nitro-DavLelNz.mjs.map → nitro-DErMq_Zj.mjs.map} +1 -1
  105. package/dist/{nitro-C6Bd682U.d.mts → nitro-oZre8ab3.d.mts} +2 -2
  106. package/dist/{nitro-C6Bd682U.d.mts.map → nitro-oZre8ab3.d.mts.map} +1 -1
  107. package/dist/{nitroConfigBridge-aZ1e5upQ.mjs → nitroConfigBridge-DKk7eOn-.mjs} +1 -1
  108. package/dist/{nitroConfigBridge-aZ1e5upQ.mjs.map → nitroConfigBridge-DKk7eOn-.mjs.map} +1 -1
  109. package/dist/nodeResponse-BkkionWl.mjs +42 -0
  110. package/dist/nodeResponse-BkkionWl.mjs.map +1 -0
  111. package/dist/nuxt/module.d.mts +28 -1
  112. package/dist/nuxt/module.d.mts.map +1 -1
  113. package/dist/nuxt/module.mjs +11 -4
  114. package/dist/nuxt/module.mjs.map +1 -1
  115. package/dist/package-v_MmOZeA.mjs +7 -0
  116. package/dist/package-v_MmOZeA.mjs.map +1 -0
  117. package/dist/{parseError-B-dKF6Fd.d.mts → parseError-yVZ58wIK.d.mts} +2 -2
  118. package/dist/parseError-yVZ58wIK.d.mts.map +1 -0
  119. package/dist/react-router/index.d.mts +2 -2
  120. package/dist/react-router/index.mjs +2 -2
  121. package/dist/{routes-B48wm7Pb.mjs → routes-CnIgYWf8.mjs} +1 -1
  122. package/dist/{routes-B48wm7Pb.mjs.map → routes-CnIgYWf8.mjs.map} +1 -1
  123. package/dist/runtime/client/log.d.mts +1 -1
  124. package/dist/runtime/server/routes/_evlog/stream-info.get.d.mts +18 -0
  125. package/dist/runtime/server/routes/_evlog/stream-info.get.d.mts.map +1 -0
  126. package/dist/runtime/server/routes/_evlog/stream-info.get.mjs +28 -0
  127. package/dist/runtime/server/routes/_evlog/stream-info.get.mjs.map +1 -0
  128. package/dist/runtime/server/useLogger.d.mts +1 -1
  129. package/dist/runtime/utils/parseError.d.mts +2 -2
  130. package/dist/{severity-BYWZ96Sb.mjs → severity-R5Egq3qz.mjs} +1 -1
  131. package/dist/{severity-BYWZ96Sb.mjs.map → severity-R5Egq3qz.mjs.map} +1 -1
  132. package/dist/{storage-BT-3fT1-.mjs → storage-Dwinmg8P.mjs} +1 -1
  133. package/dist/{storage-BT-3fT1-.mjs.map → storage-Dwinmg8P.mjs.map} +1 -1
  134. package/dist/stream.d.mts +185 -0
  135. package/dist/stream.d.mts.map +1 -0
  136. package/dist/stream.mjs +374 -0
  137. package/dist/stream.mjs.map +1 -0
  138. package/dist/sveltekit/index.d.mts +2 -2
  139. package/dist/sveltekit/index.mjs +3 -3
  140. package/dist/toolkit.d.mts +42 -7
  141. package/dist/toolkit.d.mts.map +1 -1
  142. package/dist/toolkit.mjs +10 -9
  143. package/dist/types.d.mts +2 -2
  144. package/dist/{useLogger-CoNgTjp5.d.mts → useLogger-BsPL4AQm.d.mts} +2 -2
  145. package/dist/{useLogger-CoNgTjp5.d.mts.map → useLogger-BsPL4AQm.d.mts.map} +1 -1
  146. package/dist/{utils-Db4qhBWn.d.mts → utils-DLCeShxL.d.mts} +2 -2
  147. package/dist/{utils-Db4qhBWn.d.mts.map → utils-DLCeShxL.d.mts.map} +1 -1
  148. package/dist/utils.d.mts +1 -1
  149. package/dist/vite/index.d.mts +1 -1
  150. package/dist/workers.d.mts +1 -1
  151. package/package.json +17 -1
  152. package/dist/drain-ByWUeOQC.mjs.map +0 -1
  153. package/dist/parseError-B-dKF6Fd.d.mts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"storage-BT-3fT1-.mjs","names":[],"sources":["../src/shared/storage.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { RequestLogger } from '../types'\n\n/**\n * Create a request-scoped `AsyncLocalStorage` and matching `useLogger`\n * accessor. Every framework that exposes `useLogger()` (Express, Fastify,\n * NestJS, SvelteKit) calls this once at module level.\n *\n * @param contextHint - Appended to the error message when `useLogger()` is\n * called outside of a request, e.g. `\"middleware context. Make sure\n * app.use(evlog()) is registered before your routes.\"`.\n */\nexport function createLoggerStorage(contextHint: string) {\n const storage = new AsyncLocalStorage<RequestLogger>()\n\n function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {\n const logger = storage.getStore()\n if (!logger) {\n throw new Error(\n `[evlog] useLogger() was called outside of an evlog ${contextHint}`,\n )\n }\n return logger as RequestLogger<T>\n }\n\n return { storage, useLogger }\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,oBAAoB,aAAqB;CACvD,MAAM,UAAU,IAAI,mBAAkC;CAEtD,SAAS,YAA0E;EACjF,MAAM,SAAS,QAAQ,UAAU;AACjC,MAAI,CAAC,OACH,OAAM,IAAI,MACR,sDAAsD,cACvD;AAEH,SAAO;;AAGT,QAAO;EAAE;EAAS;EAAW"}
1
+ {"version":3,"file":"storage-Dwinmg8P.mjs","names":[],"sources":["../src/shared/storage.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { RequestLogger } from '../types'\n\n/**\n * Create a request-scoped `AsyncLocalStorage` and matching `useLogger`\n * accessor. Every framework that exposes `useLogger()` (Express, Fastify,\n * NestJS, SvelteKit) calls this once at module level.\n *\n * @param contextHint - Appended to the error message when `useLogger()` is\n * called outside of a request, e.g. `\"middleware context. Make sure\n * app.use(evlog()) is registered before your routes.\"`.\n */\nexport function createLoggerStorage(contextHint: string) {\n const storage = new AsyncLocalStorage<RequestLogger>()\n\n function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {\n const logger = storage.getStore()\n if (!logger) {\n throw new Error(\n `[evlog] useLogger() was called outside of an evlog ${contextHint}`,\n )\n }\n return logger as RequestLogger<T>\n }\n\n return { storage, useLogger }\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,oBAAoB,aAAqB;CACvD,MAAM,UAAU,IAAI,mBAAkC;CAEtD,SAAS,YAA0E;EACjF,MAAM,SAAS,QAAQ,UAAU;AACjC,MAAI,CAAC,OACH,OAAM,IAAI,MACR,sDAAsD,cACvD;AAEH,SAAO;;AAGT,QAAO;EAAE;EAAS;EAAW"}
@@ -0,0 +1,185 @@
1
+ import { I as DrainContext, ct as WideEvent } from "./audit-CC8nfazi.mjs";
2
+
3
+ //#region src/stream.d.ts
4
+ /** Configuration accepted by {@link createStreamDrain}. */
5
+ interface StreamDrainOptions {
6
+ /**
7
+ * Number of recent events kept in the ring buffer and replayed to new
8
+ * subscribers via {@link StreamDrain.recent}. Set to `0` to disable
9
+ * replay.
10
+ * @default 500
11
+ */
12
+ buffer?: number;
13
+ /**
14
+ * Optional predicate run on each drained event — return `false` to skip
15
+ * the event entirely (it is neither buffered nor delivered).
16
+ */
17
+ filter?: (event: WideEvent) => boolean;
18
+ /**
19
+ * Per-subscriber queue size for the {@link StreamDrain.events} async
20
+ * iterator. When the consumer falls behind by more than this many events,
21
+ * the oldest queued events are dropped — the drain is never blocked.
22
+ * @default 1000
23
+ */
24
+ perSubscriberQueue?: number;
25
+ }
26
+ /** Live drain that exposes its events to in-process subscribers. */
27
+ interface StreamDrain {
28
+ /**
29
+ * Drain callback. Pass to `nitroApp.hooks.hook('evlog:drain', stream.drain)`
30
+ * or to a plugin via `drainPlugin('stream', stream.drain)`.
31
+ */
32
+ drain: (ctx: DrainContext | DrainContext[]) => Promise<void>;
33
+ /**
34
+ * Register a synchronous listener. Errors thrown by the listener are
35
+ * caught and logged — they never affect other subscribers or the drain.
36
+ * @returns Unsubscribe function.
37
+ */
38
+ subscribe: (listener: (event: WideEvent) => void) => () => void;
39
+ /**
40
+ * Async iterator over live events. Each call returns a fresh iterator —
41
+ * past events from the ring buffer are NOT replayed. Combine with
42
+ * {@link StreamDrain.recent} to seed history.
43
+ *
44
+ * Calling `return()` (e.g. via `break`) cleanly unsubscribes.
45
+ */
46
+ events: () => AsyncIterableIterator<WideEvent>;
47
+ /**
48
+ * Snapshot of buffered events (oldest first, most recent last). Useful
49
+ * to seed a new connection / UI panel before switching to live updates.
50
+ */
51
+ recent: () => readonly WideEvent[];
52
+ /** Number of currently active subscribers (sync + async iterators). */
53
+ readonly subscriberCount: number;
54
+ /** Number of events dropped because the buffer was disabled or wrapped. */
55
+ readonly droppedCount: number;
56
+ /**
57
+ * Disconnect all subscribers and end any pending async iterators. The
58
+ * stream itself remains usable — new subscriptions still work.
59
+ */
60
+ close: () => void;
61
+ }
62
+ /**
63
+ * Create an in-process {@link StreamDrain}. Multiple stream drains can
64
+ * coexist in the same process — they are fully independent.
65
+ */
66
+ declare function createStreamDrain(options?: StreamDrainOptions): StreamDrain;
67
+ /**
68
+ * Lazily create / return the process-wide default {@link StreamDrain}.
69
+ *
70
+ * Used by built-in framework integrations (Nuxt SSE bridge, devtools panel)
71
+ * so they share a single buffer. Custom code can subscribe to this stream
72
+ * to observe everything draining through evlog without registering a new
73
+ * drain.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * import { getDefaultStream } from 'evlog/stream'
78
+ *
79
+ * getDefaultStream().subscribe((event) => console.log(event.action))
80
+ * ```
81
+ */
82
+ declare function getDefaultStream(options?: StreamDrainOptions): StreamDrain;
83
+ /**
84
+ * Replace or clear the default stream. Pass `null` to reset (mostly useful
85
+ * in tests).
86
+ */
87
+ declare function setDefaultStream(stream: StreamDrain | null): void;
88
+ /** Configuration accepted by {@link startStreamServer}. */
89
+ interface StreamServerOptions {
90
+ /**
91
+ * TCP port to listen on. `0` (default) lets the OS pick an ephemeral port —
92
+ * the actual port is exposed on the returned `port` / `url` and printed in
93
+ * the startup banner.
94
+ * @default 0
95
+ */
96
+ port?: number;
97
+ /**
98
+ * Listen address. Default `127.0.0.1` — local-only, never exposed to the
99
+ * LAN. Override to `0.0.0.0` only if you understand the security
100
+ * implications.
101
+ * @default '127.0.0.1'
102
+ */
103
+ host?: string;
104
+ /**
105
+ * Bearer token. When set, the server requires `Authorization: Bearer
106
+ * <token>` on every request and 401s otherwise. When unset, only
107
+ * connections from `127.0.0.1` (and equivalent local hosts) are
108
+ * accepted — same-origin policy applies on non-local hosts.
109
+ */
110
+ token?: string;
111
+ /**
112
+ * Heartbeat interval (ms) sent as `event: ping` to keep the connection
113
+ * alive through proxies and detect disconnects.
114
+ * @default 15000
115
+ */
116
+ heartbeatMs?: number;
117
+ /**
118
+ * Ring buffer size kept on the underlying default stream — replayed for
119
+ * late-joining clients via `?since=<iso>`.
120
+ * @default 500
121
+ */
122
+ buffer?: number;
123
+ /**
124
+ * Print `[evlog] Stream → http://...` at startup.
125
+ * @default true
126
+ */
127
+ banner?: boolean;
128
+ /**
129
+ * Directory where `stream.url` is written so external tools can discover
130
+ * the server. Set to `false` to disable.
131
+ * @default '.evlog'
132
+ */
133
+ urlFileDir?: string | false;
134
+ }
135
+ /** Return value of {@link startStreamServer}. */
136
+ interface StreamServer {
137
+ /** Listening URL, e.g. `http://127.0.0.1:51203`. */
138
+ readonly url: string;
139
+ /** Resolved TCP port (useful when `port: 0`). */
140
+ readonly port: number;
141
+ /**
142
+ * Drain function — register on `evlog:drain` (Nitro / Nuxt), pass to
143
+ * `initLogger({ drain })` (Next / standalone), or call directly.
144
+ *
145
+ * Equivalent to the underlying default stream's drain — events flow
146
+ * through and reach every connected SSE client.
147
+ */
148
+ readonly drain: (ctx: DrainContext | DrainContext[]) => Promise<void>;
149
+ /** The underlying in-process {@link StreamDrain}. */
150
+ readonly stream: StreamDrain;
151
+ /** Stop the server, remove the URL file, and unsubscribe all clients. */
152
+ close: () => Promise<void>;
153
+ }
154
+ /**
155
+ * Start a local HTTP server that exposes the default in-process stream over
156
+ * Server-Sent Events. Framework-agnostic: works in any Node / Bun process
157
+ * (Nuxt, Next.js, Hono, Express, Fastify, raw scripts).
158
+ *
159
+ * The server binds to `127.0.0.1` on an ephemeral port by default — local
160
+ * tools and browser tabs from the same machine can connect, but the LAN
161
+ * cannot reach it.
162
+ *
163
+ * Idempotent: subsequent calls return the same instance until `close()` is
164
+ * called.
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * import { startStreamServer } from 'evlog/stream'
169
+ *
170
+ * const server = await startStreamServer()
171
+ * console.log(server.url) // → http://127.0.0.1:51203
172
+ *
173
+ * // Hook into your drain pipeline (Nitro example):
174
+ * nitroApp.hooks.hook('evlog:drain', server.drain)
175
+ *
176
+ * // Or for a standalone script with initLogger:
177
+ * initLogger({ drain: server.drain })
178
+ * ```
179
+ */
180
+ declare function startStreamServer(options?: StreamServerOptions): Promise<StreamServer>;
181
+ /** @internal Used by tests to reset state between specs. */
182
+ declare function resetStreamServerForTests(): void;
183
+ //#endregion
184
+ export { StreamDrain, StreamDrainOptions, StreamServer, StreamServerOptions, createStreamDrain, getDefaultStream, resetStreamServerForTests, setDefaultStream, startStreamServer };
185
+ //# sourceMappingURL=stream.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.d.mts","names":[],"sources":["../src/stream.ts"],"mappings":";;;;UA0BiB,kBAAA;EA+CiB;;;;;;EAxChC,MAAA;EA2BA;;;;EAtBA,MAAA,IAAU,KAAA,EAAO,SAAA;EA8BH;;;;;;EAvBd,kBAAA;AAAA;;UAIe,WAAA;EAgDgB;;;;EA3C/B,KAAA,GAAQ,GAAA,EAAK,YAAA,GAAe,YAAA,OAAmB,OAAA;EA2CoB;;;AAsJrE;;EA3LE,SAAA,GAAY,QAAA,GAAW,KAAA,EAAO,SAAA;EA2L2C;;;;;;AAS3E;EA5LE,MAAA,QAAc,qBAAA,CAAsB,SAAA;;;;AAkMtC;EA7LE,MAAA,iBAAuB,SAAA;;WAEd,eAAA;EAkMT;EAAA,SAhMS,YAAA;EA8MT;;;;EAzMA,KAAA;AAAA;;AAoOF;;;iBArNgB,iBAAA,CAAkB,OAAA,GAAS,kBAAA,GAA0B,WAAA;;;;;;;;;;;;;;;;iBAsJrD,gBAAA,CAAiB,OAAA,GAAU,kBAAA,GAAqB,WAAA;;;;AA6HhE;iBApHgB,gBAAA,CAAiB,MAAA,EAAQ,WAAA;;UAMxB,mBAAA;EA8GmE;;;;;;EAvGlF,IAAA;EAuGkF;;;AAyMpF;;;EAzSE,IAAA;EAySuC;;;;;;EAlSvC,KAAA;;;;;;EAMA,WAAA;;;;;;EAMA,MAAA;;;;;EAKA,MAAA;;;;;;EAMA,UAAA;AAAA;;UAIe,YAAA;;WAEN,GAAA;;WAEA,IAAA;;;;;;;;WAQA,KAAA,GAAQ,GAAA,EAAK,YAAA,GAAe,YAAA,OAAmB,OAAA;;WAE/C,MAAA,EAAQ,WAAA;;EAEjB,KAAA,QAAa,OAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8CO,iBAAA,CAAkB,OAAA,GAAS,mBAAA,GAA2B,OAAA,CAAQ,YAAA;;iBAyMpE,yBAAA,CAAA"}
@@ -0,0 +1,374 @@
1
+ import { n as EVLOG_VERSION } from "./http-6umVAKDW.mjs";
2
+ //#region src/stream.ts
3
+ const DEFAULT_BUFFER = 500;
4
+ const DEFAULT_QUEUE = 1e3;
5
+ /**
6
+ * Create an in-process {@link StreamDrain}. Multiple stream drains can
7
+ * coexist in the same process — they are fully independent.
8
+ */
9
+ function createStreamDrain(options = {}) {
10
+ const bufferSize = Math.max(0, Math.floor(options.buffer ?? DEFAULT_BUFFER));
11
+ const queueLimit = Math.max(1, Math.floor(options.perSubscriberQueue ?? DEFAULT_QUEUE));
12
+ const { filter } = options;
13
+ const buffer = [];
14
+ const syncListeners = /* @__PURE__ */ new Set();
15
+ const asyncSubscribers = /* @__PURE__ */ new Set();
16
+ let dropped = 0;
17
+ function publish(event) {
18
+ if (filter && !filter(event)) return;
19
+ if (bufferSize > 0) {
20
+ buffer.push(event);
21
+ while (buffer.length > bufferSize) {
22
+ buffer.shift();
23
+ dropped++;
24
+ }
25
+ }
26
+ for (const listener of syncListeners) try {
27
+ listener(event);
28
+ } catch (err) {
29
+ console.error("[evlog/stream] subscriber threw:", err);
30
+ }
31
+ for (const sub of asyncSubscribers) sub.push(event);
32
+ }
33
+ const drain = (ctx) => {
34
+ const contexts = Array.isArray(ctx) ? ctx : [ctx];
35
+ for (const c of contexts) if (c?.event) publish(c.event);
36
+ return Promise.resolve();
37
+ };
38
+ const subscribe = (listener) => {
39
+ syncListeners.add(listener);
40
+ return () => {
41
+ syncListeners.delete(listener);
42
+ };
43
+ };
44
+ function createAsyncIterator() {
45
+ const queue = [];
46
+ let pendingResolve = null;
47
+ let closed = false;
48
+ const subscriber = {
49
+ push(event) {
50
+ if (closed) return;
51
+ if (pendingResolve) {
52
+ const resolve = pendingResolve;
53
+ pendingResolve = null;
54
+ resolve({
55
+ value: event,
56
+ done: false
57
+ });
58
+ return;
59
+ }
60
+ queue.push(event);
61
+ while (queue.length > queueLimit) {
62
+ queue.shift();
63
+ dropped++;
64
+ }
65
+ },
66
+ end() {
67
+ if (closed) return;
68
+ closed = true;
69
+ if (pendingResolve) {
70
+ const resolve = pendingResolve;
71
+ pendingResolve = null;
72
+ resolve({
73
+ value: void 0,
74
+ done: true
75
+ });
76
+ }
77
+ }
78
+ };
79
+ asyncSubscribers.add(subscriber);
80
+ const iterator = {
81
+ [Symbol.asyncIterator]() {
82
+ return iterator;
83
+ },
84
+ next() {
85
+ if (queue.length > 0) return Promise.resolve({
86
+ value: queue.shift(),
87
+ done: false
88
+ });
89
+ if (closed) return Promise.resolve({
90
+ value: void 0,
91
+ done: true
92
+ });
93
+ return new Promise((resolve) => {
94
+ pendingResolve = resolve;
95
+ });
96
+ },
97
+ return() {
98
+ closed = true;
99
+ asyncSubscribers.delete(subscriber);
100
+ if (pendingResolve) {
101
+ const resolve = pendingResolve;
102
+ pendingResolve = null;
103
+ resolve({
104
+ value: void 0,
105
+ done: true
106
+ });
107
+ }
108
+ return Promise.resolve({
109
+ value: void 0,
110
+ done: true
111
+ });
112
+ }
113
+ };
114
+ return iterator;
115
+ }
116
+ return {
117
+ drain,
118
+ subscribe,
119
+ events: createAsyncIterator,
120
+ recent: () => buffer.slice(),
121
+ get subscriberCount() {
122
+ return syncListeners.size + asyncSubscribers.size;
123
+ },
124
+ get droppedCount() {
125
+ return dropped;
126
+ },
127
+ close() {
128
+ syncListeners.clear();
129
+ for (const sub of asyncSubscribers) sub.end();
130
+ asyncSubscribers.clear();
131
+ }
132
+ };
133
+ }
134
+ let defaultStream = null;
135
+ /**
136
+ * Lazily create / return the process-wide default {@link StreamDrain}.
137
+ *
138
+ * Used by built-in framework integrations (Nuxt SSE bridge, devtools panel)
139
+ * so they share a single buffer. Custom code can subscribe to this stream
140
+ * to observe everything draining through evlog without registering a new
141
+ * drain.
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * import { getDefaultStream } from 'evlog/stream'
146
+ *
147
+ * getDefaultStream().subscribe((event) => console.log(event.action))
148
+ * ```
149
+ */
150
+ function getDefaultStream(options) {
151
+ if (!defaultStream) defaultStream = createStreamDrain(options);
152
+ return defaultStream;
153
+ }
154
+ /**
155
+ * Replace or clear the default stream. Pass `null` to reset (mostly useful
156
+ * in tests).
157
+ */
158
+ function setDefaultStream(stream) {
159
+ defaultStream?.close();
160
+ defaultStream = stream;
161
+ }
162
+ const STREAM_DEFAULTS = {
163
+ port: 0,
164
+ host: "127.0.0.1",
165
+ heartbeatMs: 15e3,
166
+ buffer: 500,
167
+ banner: true,
168
+ urlFileDir: ".evlog"
169
+ };
170
+ let activeStreamServer = null;
171
+ function isLocalHost(host) {
172
+ if (!host) return false;
173
+ const lower = host.toLowerCase();
174
+ return lower.startsWith("localhost") || lower.startsWith("127.0.0.1") || lower.startsWith("[::1]");
175
+ }
176
+ /**
177
+ * Start a local HTTP server that exposes the default in-process stream over
178
+ * Server-Sent Events. Framework-agnostic: works in any Node / Bun process
179
+ * (Nuxt, Next.js, Hono, Express, Fastify, raw scripts).
180
+ *
181
+ * The server binds to `127.0.0.1` on an ephemeral port by default — local
182
+ * tools and browser tabs from the same machine can connect, but the LAN
183
+ * cannot reach it.
184
+ *
185
+ * Idempotent: subsequent calls return the same instance until `close()` is
186
+ * called.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * import { startStreamServer } from 'evlog/stream'
191
+ *
192
+ * const server = await startStreamServer()
193
+ * console.log(server.url) // → http://127.0.0.1:51203
194
+ *
195
+ * // Hook into your drain pipeline (Nitro example):
196
+ * nitroApp.hooks.hook('evlog:drain', server.drain)
197
+ *
198
+ * // Or for a standalone script with initLogger:
199
+ * initLogger({ drain: server.drain })
200
+ * ```
201
+ */
202
+ async function startStreamServer(options = {}) {
203
+ if (activeStreamServer) return activeStreamServer;
204
+ const opts = {
205
+ ...STREAM_DEFAULTS,
206
+ ...options
207
+ };
208
+ const stream = getDefaultStream({ buffer: opts.buffer });
209
+ const { createServer } = await import("node:http");
210
+ const { writeFile, unlink, mkdir } = await import("node:fs/promises");
211
+ const { unlinkSync } = await import("node:fs");
212
+ const { join } = await import("node:path");
213
+ function envelopeFrame(type, data) {
214
+ return `data: ${JSON.stringify({
215
+ evlog: "1",
216
+ type,
217
+ data
218
+ })}\n\n`;
219
+ }
220
+ function writeCors(res) {
221
+ res.setHeader("Access-Control-Allow-Origin", "*");
222
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
223
+ res.setHeader("Access-Control-Allow-Headers", "Authorization, Accept");
224
+ }
225
+ const server = createServer((req, res) => {
226
+ if (req.method === "OPTIONS") {
227
+ writeCors(res);
228
+ res.writeHead(204);
229
+ res.end();
230
+ return;
231
+ }
232
+ if (req.method !== "GET") {
233
+ res.writeHead(405);
234
+ res.end("method not allowed");
235
+ return;
236
+ }
237
+ if (opts.token) {
238
+ if (req.headers.authorization !== `Bearer ${opts.token}`) {
239
+ writeCors(res);
240
+ res.writeHead(401);
241
+ res.end("unauthorized");
242
+ return;
243
+ }
244
+ } else {
245
+ const origin = typeof req.headers.origin === "string" ? req.headers.origin : null;
246
+ if (origin) {
247
+ let originHost = null;
248
+ try {
249
+ originHost = new URL(origin).host;
250
+ } catch {
251
+ originHost = null;
252
+ }
253
+ if (!isLocalHost(originHost ?? void 0)) {
254
+ writeCors(res);
255
+ res.writeHead(403);
256
+ res.end("forbidden");
257
+ return;
258
+ }
259
+ }
260
+ }
261
+ const url = new URL(req.url ?? "/", "http://localhost");
262
+ if (url.pathname === "/info") {
263
+ writeCors(res);
264
+ res.writeHead(200, { "Content-Type": "application/json" });
265
+ res.end(JSON.stringify({
266
+ evlogVersion: EVLOG_VERSION,
267
+ bufferSize: opts.buffer,
268
+ heartbeatMs: opts.heartbeatMs
269
+ }));
270
+ return;
271
+ }
272
+ writeCors(res);
273
+ res.writeHead(200, {
274
+ "Content-Type": "text/event-stream; charset=utf-8",
275
+ "Cache-Control": "no-cache, no-transform",
276
+ "Connection": "keep-alive",
277
+ "X-Accel-Buffering": "no",
278
+ "X-Evlog-Version": EVLOG_VERSION
279
+ });
280
+ res.write(envelopeFrame("hello", {
281
+ evlogVersion: EVLOG_VERSION,
282
+ bufferSize: opts.buffer,
283
+ heartbeatMs: opts.heartbeatMs
284
+ }));
285
+ const since = url.searchParams.get("since");
286
+ const sinceMs = since ? Date.parse(since) : NaN;
287
+ if (Number.isFinite(sinceMs)) for (const past of stream.recent()) {
288
+ const ts = typeof past.timestamp === "string" ? Date.parse(past.timestamp) : NaN;
289
+ if (Number.isFinite(ts) && ts >= sinceMs) res.write(envelopeFrame("replay", past));
290
+ }
291
+ let closed = false;
292
+ const unsubscribe = stream.subscribe((event) => {
293
+ if (closed) return;
294
+ res.write(envelopeFrame("event", event));
295
+ });
296
+ const heartbeat = setInterval(() => {
297
+ if (closed) return;
298
+ res.write(`event: ping\ndata: ${JSON.stringify({
299
+ evlog: "1",
300
+ type: "ping",
301
+ data: { t: Date.now() }
302
+ })}\n\n`);
303
+ }, opts.heartbeatMs);
304
+ const cleanup = () => {
305
+ if (closed) return;
306
+ closed = true;
307
+ clearInterval(heartbeat);
308
+ unsubscribe();
309
+ try {
310
+ res.end();
311
+ } catch {}
312
+ };
313
+ req.on("close", cleanup);
314
+ req.on("error", cleanup);
315
+ res.on("close", cleanup);
316
+ res.on("error", cleanup);
317
+ });
318
+ const port = await new Promise((resolve, reject) => {
319
+ server.once("error", reject);
320
+ server.listen(opts.port, opts.host, () => {
321
+ const addr = server.address();
322
+ if (addr && typeof addr === "object") resolve(addr.port);
323
+ else reject(/* @__PURE__ */ new Error("[evlog/stream] failed to determine listening port"));
324
+ });
325
+ });
326
+ const url = `http://${opts.host}:${port}`;
327
+ let urlFile;
328
+ if (opts.urlFileDir !== false) try {
329
+ await mkdir(opts.urlFileDir, { recursive: true });
330
+ urlFile = join(opts.urlFileDir, "stream.url");
331
+ await writeFile(urlFile, url, "utf-8");
332
+ } catch (err) {
333
+ console.warn("[evlog/stream] failed to write stream.url:", err);
334
+ }
335
+ if (opts.banner) console.info(`\n [evlog] Stream → ${url}\n`);
336
+ const exitHandler = () => {
337
+ if (urlFile) try {
338
+ unlinkSync(urlFile);
339
+ } catch {}
340
+ server.close();
341
+ };
342
+ process.once("SIGINT", exitHandler);
343
+ process.once("SIGTERM", exitHandler);
344
+ process.once("exit", exitHandler);
345
+ const close = async () => {
346
+ process.off("SIGINT", exitHandler);
347
+ process.off("SIGTERM", exitHandler);
348
+ process.off("exit", exitHandler);
349
+ if (urlFile) try {
350
+ await unlink(urlFile);
351
+ } catch {}
352
+ await new Promise((resolve, reject) => {
353
+ server.close((err) => err ? reject(err) : resolve());
354
+ });
355
+ if (activeStreamServer === result) activeStreamServer = null;
356
+ };
357
+ const result = {
358
+ url,
359
+ port,
360
+ drain: (ctx) => stream.drain(ctx),
361
+ stream,
362
+ close
363
+ };
364
+ activeStreamServer = result;
365
+ return result;
366
+ }
367
+ /** @internal Used by tests to reset state between specs. */
368
+ function resetStreamServerForTests() {
369
+ activeStreamServer = null;
370
+ }
371
+ //#endregion
372
+ export { createStreamDrain, getDefaultStream, resetStreamServerForTests, setDefaultStream, startStreamServer };
373
+
374
+ //# sourceMappingURL=stream.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.mjs","names":[],"sources":["../src/stream.ts"],"sourcesContent":["/**\n * In-process event stream — the foundation for any local consumer (devtools,\n * dashboards, CLIs, SSE/WebSocket bridges) to observe events flowing through\n * the drain pipeline without re-implementing one.\n *\n * @example\n * ```ts\n * import { createStreamDrain } from 'evlog/stream'\n *\n * const stream = createStreamDrain({ buffer: 200 })\n * nitroApp.hooks.hook('evlog:drain', stream.drain)\n *\n * stream.subscribe((event) => {\n * if (event.level === 'error') notifyOps(event)\n * })\n *\n * for await (const event of stream.events()) {\n * console.log(event.timestamp, event.action ?? event.message)\n * }\n * ```\n */\n\nimport type { DrainContext, WideEvent } from './types'\nimport { EVLOG_VERSION } from './shared/http'\n\n/** Configuration accepted by {@link createStreamDrain}. */\nexport interface StreamDrainOptions {\n /**\n * Number of recent events kept in the ring buffer and replayed to new\n * subscribers via {@link StreamDrain.recent}. Set to `0` to disable\n * replay.\n * @default 500\n */\n buffer?: number\n /**\n * Optional predicate run on each drained event — return `false` to skip\n * the event entirely (it is neither buffered nor delivered).\n */\n filter?: (event: WideEvent) => boolean\n /**\n * Per-subscriber queue size for the {@link StreamDrain.events} async\n * iterator. When the consumer falls behind by more than this many events,\n * the oldest queued events are dropped — the drain is never blocked.\n * @default 1000\n */\n perSubscriberQueue?: number\n}\n\n/** Live drain that exposes its events to in-process subscribers. */\nexport interface StreamDrain {\n /**\n * Drain callback. Pass to `nitroApp.hooks.hook('evlog:drain', stream.drain)`\n * or to a plugin via `drainPlugin('stream', stream.drain)`.\n */\n drain: (ctx: DrainContext | DrainContext[]) => Promise<void>\n /**\n * Register a synchronous listener. Errors thrown by the listener are\n * caught and logged — they never affect other subscribers or the drain.\n * @returns Unsubscribe function.\n */\n subscribe: (listener: (event: WideEvent) => void) => () => void\n /**\n * Async iterator over live events. Each call returns a fresh iterator —\n * past events from the ring buffer are NOT replayed. Combine with\n * {@link StreamDrain.recent} to seed history.\n *\n * Calling `return()` (e.g. via `break`) cleanly unsubscribes.\n */\n events: () => AsyncIterableIterator<WideEvent>\n /**\n * Snapshot of buffered events (oldest first, most recent last). Useful\n * to seed a new connection / UI panel before switching to live updates.\n */\n recent: () => readonly WideEvent[]\n /** Number of currently active subscribers (sync + async iterators). */\n readonly subscriberCount: number\n /** Number of events dropped because the buffer was disabled or wrapped. */\n readonly droppedCount: number\n /**\n * Disconnect all subscribers and end any pending async iterators. The\n * stream itself remains usable — new subscriptions still work.\n */\n close: () => void\n}\n\nconst DEFAULT_BUFFER = 500\nconst DEFAULT_QUEUE = 1000\n\ninterface AsyncSubscriber {\n push: (event: WideEvent) => void\n end: () => void\n}\n\n/**\n * Create an in-process {@link StreamDrain}. Multiple stream drains can\n * coexist in the same process — they are fully independent.\n */\nexport function createStreamDrain(options: StreamDrainOptions = {}): StreamDrain {\n const bufferSize = Math.max(0, Math.floor(options.buffer ?? DEFAULT_BUFFER))\n const queueLimit = Math.max(1, Math.floor(options.perSubscriberQueue ?? DEFAULT_QUEUE))\n const { filter } = options\n\n const buffer: WideEvent[] = []\n const syncListeners = new Set<(event: WideEvent) => void>()\n const asyncSubscribers = new Set<AsyncSubscriber>()\n let dropped = 0\n\n function publish(event: WideEvent): void {\n if (filter && !filter(event)) return\n\n if (bufferSize > 0) {\n buffer.push(event)\n while (buffer.length > bufferSize) {\n buffer.shift()\n dropped++\n }\n }\n\n for (const listener of syncListeners) {\n try {\n listener(event)\n } catch (err) {\n console.error('[evlog/stream] subscriber threw:', err)\n }\n }\n\n for (const sub of asyncSubscribers) {\n sub.push(event)\n }\n }\n\n const drain: StreamDrain['drain'] = (ctx) => {\n const contexts = Array.isArray(ctx) ? ctx : [ctx]\n for (const c of contexts) {\n if (c?.event) publish(c.event)\n }\n return Promise.resolve()\n }\n\n const subscribe: StreamDrain['subscribe'] = (listener) => {\n syncListeners.add(listener)\n return () => {\n syncListeners.delete(listener)\n }\n }\n\n function createAsyncIterator(): AsyncIterableIterator<WideEvent> {\n const queue: WideEvent[] = []\n let pendingResolve: ((result: IteratorResult<WideEvent>) => void) | null = null\n let closed = false\n\n const subscriber: AsyncSubscriber = {\n push(event) {\n if (closed) return\n if (pendingResolve) {\n const resolve = pendingResolve\n pendingResolve = null\n resolve({ value: event, done: false })\n return\n }\n queue.push(event)\n while (queue.length > queueLimit) {\n queue.shift()\n dropped++\n }\n },\n end() {\n if (closed) return\n closed = true\n if (pendingResolve) {\n const resolve = pendingResolve\n pendingResolve = null\n resolve({ value: undefined, done: true })\n }\n },\n }\n\n asyncSubscribers.add(subscriber)\n\n const iterator: AsyncIterableIterator<WideEvent> = {\n [Symbol.asyncIterator]() {\n return iterator\n },\n next(): Promise<IteratorResult<WideEvent>> {\n if (queue.length > 0) {\n return Promise.resolve({ value: queue.shift()!, done: false })\n }\n if (closed) {\n return Promise.resolve({ value: undefined, done: true })\n }\n return new Promise((resolve) => {\n pendingResolve = resolve\n })\n },\n return(): Promise<IteratorResult<WideEvent>> {\n closed = true\n asyncSubscribers.delete(subscriber)\n if (pendingResolve) {\n const resolve = pendingResolve\n pendingResolve = null\n resolve({ value: undefined, done: true })\n }\n return Promise.resolve({ value: undefined, done: true })\n },\n }\n\n return iterator\n }\n\n return {\n drain,\n subscribe,\n events: createAsyncIterator,\n recent: () => buffer.slice(),\n get subscriberCount() {\n return syncListeners.size + asyncSubscribers.size\n },\n get droppedCount() {\n return dropped\n },\n close() {\n syncListeners.clear()\n for (const sub of asyncSubscribers) {\n sub.end()\n }\n asyncSubscribers.clear()\n },\n }\n}\n\nlet defaultStream: StreamDrain | null = null\n\n/**\n * Lazily create / return the process-wide default {@link StreamDrain}.\n *\n * Used by built-in framework integrations (Nuxt SSE bridge, devtools panel)\n * so they share a single buffer. Custom code can subscribe to this stream\n * to observe everything draining through evlog without registering a new\n * drain.\n *\n * @example\n * ```ts\n * import { getDefaultStream } from 'evlog/stream'\n *\n * getDefaultStream().subscribe((event) => console.log(event.action))\n * ```\n */\nexport function getDefaultStream(options?: StreamDrainOptions): StreamDrain {\n if (!defaultStream) defaultStream = createStreamDrain(options)\n return defaultStream\n}\n\n/**\n * Replace or clear the default stream. Pass `null` to reset (mostly useful\n * in tests).\n */\nexport function setDefaultStream(stream: StreamDrain | null): void {\n defaultStream?.close()\n defaultStream = stream\n}\n\n/** Configuration accepted by {@link startStreamServer}. */\nexport interface StreamServerOptions {\n /**\n * TCP port to listen on. `0` (default) lets the OS pick an ephemeral port —\n * the actual port is exposed on the returned `port` / `url` and printed in\n * the startup banner.\n * @default 0\n */\n port?: number\n /**\n * Listen address. Default `127.0.0.1` — local-only, never exposed to the\n * LAN. Override to `0.0.0.0` only if you understand the security\n * implications.\n * @default '127.0.0.1'\n */\n host?: string\n /**\n * Bearer token. When set, the server requires `Authorization: Bearer\n * <token>` on every request and 401s otherwise. When unset, only\n * connections from `127.0.0.1` (and equivalent local hosts) are\n * accepted — same-origin policy applies on non-local hosts.\n */\n token?: string\n /**\n * Heartbeat interval (ms) sent as `event: ping` to keep the connection\n * alive through proxies and detect disconnects.\n * @default 15000\n */\n heartbeatMs?: number\n /**\n * Ring buffer size kept on the underlying default stream — replayed for\n * late-joining clients via `?since=<iso>`.\n * @default 500\n */\n buffer?: number\n /**\n * Print `[evlog] Stream → http://...` at startup.\n * @default true\n */\n banner?: boolean\n /**\n * Directory where `stream.url` is written so external tools can discover\n * the server. Set to `false` to disable.\n * @default '.evlog'\n */\n urlFileDir?: string | false\n}\n\n/** Return value of {@link startStreamServer}. */\nexport interface StreamServer {\n /** Listening URL, e.g. `http://127.0.0.1:51203`. */\n readonly url: string\n /** Resolved TCP port (useful when `port: 0`). */\n readonly port: number\n /**\n * Drain function — register on `evlog:drain` (Nitro / Nuxt), pass to\n * `initLogger({ drain })` (Next / standalone), or call directly.\n *\n * Equivalent to the underlying default stream's drain — events flow\n * through and reach every connected SSE client.\n */\n readonly drain: (ctx: DrainContext | DrainContext[]) => Promise<void>\n /** The underlying in-process {@link StreamDrain}. */\n readonly stream: StreamDrain\n /** Stop the server, remove the URL file, and unsubscribe all clients. */\n close: () => Promise<void>\n}\n\nconst STREAM_DEFAULTS = {\n port: 0,\n host: '127.0.0.1',\n heartbeatMs: 15_000,\n buffer: 500,\n banner: true,\n urlFileDir: '.evlog' as string | false,\n}\n\nlet activeStreamServer: StreamServer | null = null\n\nfunction isLocalHost(host: string | undefined): boolean {\n if (!host) return false\n const lower = host.toLowerCase()\n return lower.startsWith('localhost') || lower.startsWith('127.0.0.1') || lower.startsWith('[::1]')\n}\n\n/**\n * Start a local HTTP server that exposes the default in-process stream over\n * Server-Sent Events. Framework-agnostic: works in any Node / Bun process\n * (Nuxt, Next.js, Hono, Express, Fastify, raw scripts).\n *\n * The server binds to `127.0.0.1` on an ephemeral port by default — local\n * tools and browser tabs from the same machine can connect, but the LAN\n * cannot reach it.\n *\n * Idempotent: subsequent calls return the same instance until `close()` is\n * called.\n *\n * @example\n * ```ts\n * import { startStreamServer } from 'evlog/stream'\n *\n * const server = await startStreamServer()\n * console.log(server.url) // → http://127.0.0.1:51203\n *\n * // Hook into your drain pipeline (Nitro example):\n * nitroApp.hooks.hook('evlog:drain', server.drain)\n *\n * // Or for a standalone script with initLogger:\n * initLogger({ drain: server.drain })\n * ```\n */\nexport async function startStreamServer(options: StreamServerOptions = {}): Promise<StreamServer> {\n if (activeStreamServer) return activeStreamServer\n\n const opts = { ...STREAM_DEFAULTS, ...options }\n const stream = getDefaultStream({ buffer: opts.buffer })\n\n const { createServer } = await import('node:http')\n const { writeFile, unlink, mkdir } = await import('node:fs/promises')\n const { unlinkSync } = await import('node:fs')\n const { join } = await import('node:path')\n\n function envelopeFrame(type: 'event' | 'replay' | 'hello', data: unknown): string {\n return `data: ${JSON.stringify({ evlog: '1', type, data })}\\n\\n`\n }\n\n function writeCors(res: import('node:http').ServerResponse): void {\n res.setHeader('Access-Control-Allow-Origin', '*')\n res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'Authorization, Accept')\n }\n\n const server = createServer((req, res) => {\n if (req.method === 'OPTIONS') {\n writeCors(res)\n res.writeHead(204)\n res.end()\n return\n }\n\n if (req.method !== 'GET') {\n res.writeHead(405)\n res.end('method not allowed')\n return\n }\n\n if (opts.token) {\n if (req.headers.authorization !== `Bearer ${opts.token}`) {\n writeCors(res)\n res.writeHead(401)\n res.end('unauthorized')\n return\n }\n } else {\n const origin = typeof req.headers.origin === 'string' ? req.headers.origin : null\n if (origin) {\n let originHost: string | null = null\n try {\n originHost = new URL(origin).host\n } catch {\n originHost = null\n }\n if (!isLocalHost(originHost ?? undefined)) {\n writeCors(res)\n res.writeHead(403)\n res.end('forbidden')\n return\n }\n }\n }\n\n const url = new URL(req.url ?? '/', 'http://localhost')\n\n if (url.pathname === '/info') {\n writeCors(res)\n res.writeHead(200, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify({\n evlogVersion: EVLOG_VERSION,\n bufferSize: opts.buffer,\n heartbeatMs: opts.heartbeatMs,\n }))\n return\n }\n\n writeCors(res)\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream; charset=utf-8',\n 'Cache-Control': 'no-cache, no-transform',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no',\n 'X-Evlog-Version': EVLOG_VERSION,\n })\n\n res.write(envelopeFrame('hello', {\n evlogVersion: EVLOG_VERSION,\n bufferSize: opts.buffer,\n heartbeatMs: opts.heartbeatMs,\n }))\n\n const since = url.searchParams.get('since')\n const sinceMs = since ? Date.parse(since) : Number.NaN\n if (Number.isFinite(sinceMs)) {\n for (const past of stream.recent()) {\n const ts = typeof past.timestamp === 'string' ? Date.parse(past.timestamp) : Number.NaN\n if (Number.isFinite(ts) && ts >= sinceMs) {\n res.write(envelopeFrame('replay', past))\n }\n }\n }\n\n let closed = false\n const unsubscribe = stream.subscribe((event) => {\n if (closed) return\n res.write(envelopeFrame('event', event))\n })\n\n const heartbeat = setInterval(() => {\n if (closed) return\n res.write(`event: ping\\ndata: ${JSON.stringify({ evlog: '1', type: 'ping', data: { t: Date.now() } })}\\n\\n`)\n }, opts.heartbeatMs)\n\n const cleanup = () => {\n if (closed) return\n closed = true\n clearInterval(heartbeat)\n unsubscribe()\n try {\n res.end()\n } catch {\n // noop\n }\n }\n\n req.on('close', cleanup)\n req.on('error', cleanup)\n res.on('close', cleanup)\n res.on('error', cleanup)\n })\n\n const port: number = await new Promise((resolve, reject) => {\n server.once('error', reject)\n server.listen(opts.port, opts.host, () => {\n const addr = server.address()\n if (addr && typeof addr === 'object') {\n resolve(addr.port)\n } else {\n reject(new Error('[evlog/stream] failed to determine listening port'))\n }\n })\n })\n\n const url = `http://${opts.host}:${port}`\n\n let urlFile: string | undefined\n if (opts.urlFileDir !== false) {\n try {\n await mkdir(opts.urlFileDir, { recursive: true })\n urlFile = join(opts.urlFileDir, 'stream.url')\n await writeFile(urlFile, url, 'utf-8')\n } catch (err) {\n console.warn('[evlog/stream] failed to write stream.url:', err)\n }\n }\n\n if (opts.banner) {\n console.info(`\\n [evlog] Stream → ${url}\\n`)\n }\n\n const exitHandler = () => {\n if (urlFile) {\n try {\n unlinkSync(urlFile)\n } catch {\n // noop\n }\n }\n server.close()\n }\n process.once('SIGINT', exitHandler)\n process.once('SIGTERM', exitHandler)\n process.once('exit', exitHandler)\n\n const close = async (): Promise<void> => {\n process.off('SIGINT', exitHandler)\n process.off('SIGTERM', exitHandler)\n process.off('exit', exitHandler)\n if (urlFile) {\n try {\n await unlink(urlFile)\n } catch {\n // noop\n }\n }\n await new Promise<void>((resolve, reject) => {\n server.close(err => err ? reject(err) : resolve())\n })\n if (activeStreamServer === result) activeStreamServer = null\n }\n\n const result: StreamServer = {\n url,\n port,\n drain: ctx => stream.drain(ctx),\n stream,\n close,\n }\n\n activeStreamServer = result\n return result\n}\n\n/** @internal Used by tests to reset state between specs. */\nexport function resetStreamServerForTests(): void {\n activeStreamServer = null\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { Y as RequestLogger } from "../audit-X1uUukm3.mjs";
2
- import { t as BaseEvlogOptions } from "../middleware-CAQHJRN1.mjs";
1
+ import { $ as RequestLogger } from "../audit-CC8nfazi.mjs";
2
+ import { t as BaseEvlogOptions } from "../middleware-U-lIAzHg.mjs";
3
3
 
4
4
  //#region src/sveltekit/index.d.ts
5
5
  declare const useLogger: <T extends object = Record<string, unknown>>() => RequestLogger<T>;
@@ -1,9 +1,9 @@
1
1
  import { t as extractSafeHeaders } from "../headers-CU-QqnYg.mjs";
2
2
  import { EvlogError } from "../error.mjs";
3
3
  import { t as extractErrorStatus } from "../errors-BQgyQ9xe.mjs";
4
- import { n as serializeEvlogErrorResponse, t as resolveEvlogError } from "../nitro-DavLelNz.mjs";
5
- import { r as createMiddlewareLogger, t as attachForkToLogger } from "../fork-DPN8aL8O.mjs";
6
- import { t as createLoggerStorage } from "../storage-BT-3fT1-.mjs";
4
+ import { n as serializeEvlogErrorResponse, t as resolveEvlogError } from "../nitro-DErMq_Zj.mjs";
5
+ import { r as createMiddlewareLogger, t as attachForkToLogger } from "../fork-8u_zFOJq.mjs";
6
+ import { t as createLoggerStorage } from "../storage-Dwinmg8P.mjs";
7
7
  //#region src/sveltekit/index.ts
8
8
  const { storage, useLogger } = createLoggerStorage("handle context. Make sure evlog() handle is added to your hooks.server.ts.");
9
9
  /**