Skip to content

Commit 4385dbc

Browse files
shubh73HugoRCD
andauthored
fix(evlog): replace require() with dynamic import() for workers compat (#228)
Co-authored-by: Hugo Richard <hugo.richard@vercel.com>
1 parent 767ba27 commit 4385dbc

File tree

9 files changed

+58
-35
lines changed

9 files changed

+58
-35
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"evlog": patch
3+
---
4+
5+
Resolve Nitro runtime config in drain adapters via dynamic `import()` (Cloudflare Workers and other runtimes without `require`). Cache Nitro module namespaces after first load to avoid repeated imports on every drain. Fix HyperDX drain to `await` `resolveAdapterConfig()` so env/runtime config is applied when using `createHyperDXDrain()` without inline overrides.

packages/evlog/src/adapters/_config.ts

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,50 @@
11
/**
2-
* Try to get runtime config from Nitro/Nuxt environment.
3-
* Supports both Nitro v2 (nitropack/runtime) and Nitro v3 (nitro/runtime-config).
4-
* Returns undefined if not in a Nitro context.
2+
* Nitro runtime modules resolved via dynamic `import()` (Workers-safe: avoids a bundler-injected
3+
* `createRequire` polyfill from sync `require()`). Module namespaces are cached after first
4+
* successful load; `useRuntimeConfig()` is still invoked on each call so config stays current.
5+
*
6+
* Drain handlers remain non-blocking for the HTTP response when the host provides `waitUntil`
7+
* (see Nitro plugin); the extra `await` here only sequences work inside that background drain.
58
*/
6-
export function getRuntimeConfig(): Record<string, any> | undefined {
7-
try {
8-
// eslint-disable-next-line @typescript-eslint/no-require-imports
9-
const { useRuntimeConfig } = require('nitropack/runtime')
10-
return useRuntimeConfig()
11-
} catch {
12-
// nitropack not available — try Nitro v3
9+
let nitropackRuntime: typeof import('nitropack/runtime') | null | undefined
10+
let nitroV3Runtime: typeof import('nitro/runtime-config') | null | undefined
11+
12+
export async function getRuntimeConfig(): Promise<Record<string, any> | undefined> {
13+
if (nitropackRuntime === undefined) {
14+
try {
15+
nitropackRuntime = await import('nitropack/runtime')
16+
} catch {
17+
nitropackRuntime = null
18+
}
19+
}
20+
if (nitropackRuntime) {
21+
return nitropackRuntime.useRuntimeConfig()
1322
}
1423

15-
try {
16-
// eslint-disable-next-line @typescript-eslint/no-require-imports
17-
const { useRuntimeConfig } = require('nitro/runtime-config')
18-
return useRuntimeConfig()
19-
} catch {
20-
return undefined
24+
if (nitroV3Runtime === undefined) {
25+
try {
26+
nitroV3Runtime = await import('nitro/runtime-config')
27+
} catch {
28+
nitroV3Runtime = null
29+
}
2130
}
31+
if (nitroV3Runtime) {
32+
return nitroV3Runtime.useRuntimeConfig()
33+
}
34+
return undefined
2235
}
2336

2437
export interface ConfigField<T> {
2538
key: keyof T & string
2639
env?: string[]
2740
}
2841

29-
export function resolveAdapterConfig<T>(
42+
export async function resolveAdapterConfig<T>(
3043
namespace: string,
3144
fields: ConfigField<T>[],
3245
overrides?: Partial<T>,
33-
): Partial<T> {
34-
const runtimeConfig = getRuntimeConfig()
46+
): Promise<Partial<T>> {
47+
const runtimeConfig = await getRuntimeConfig()
3548
const evlogNs = runtimeConfig?.evlog?.[namespace]
3649
const rootNs = runtimeConfig?.[namespace]
3750

packages/evlog/src/adapters/_drain.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@ import type { DrainContext, WideEvent } from '../types'
22

33
export interface DrainOptions<TConfig> {
44
name: string
5-
resolve: () => TConfig | null
5+
resolve: () => TConfig | null | Promise<TConfig | null>
66
send: (events: WideEvent[], config: TConfig) => Promise<void>
77
}
88

9+
/**
10+
* Build a drain callback for `evlog:drain` (or `initLogger({ drain })`).
11+
* The returned function is async so `resolve` can load Nitro runtime config; hosts typically attach
12+
* the resulting promise to `waitUntil` so the HTTP response is not blocked (see Nitro plugin).
13+
*/
914
export function defineDrain<TConfig>(options: DrainOptions<TConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void> {
1015
return async (ctx: DrainContext | DrainContext[]) => {
1116
const contexts = Array.isArray(ctx) ? ctx : [ctx]
1217
if (contexts.length === 0) return
1318

14-
const config = options.resolve()
19+
const config = await options.resolve()
1520
if (!config) return
1621

1722
try {

packages/evlog/src/adapters/axiom.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ const AXIOM_FIELDS: ConfigField<ResolvedAxiomConfig>[] = [
7575
export function createAxiomDrain(overrides?: Partial<AxiomConfig>) {
7676
return defineDrain<AxiomConfig>({
7777
name: 'axiom',
78-
resolve: () => {
79-
const config = resolveAdapterConfig<ResolvedAxiomConfig>(
78+
resolve: async () => {
79+
const config = await resolveAdapterConfig<ResolvedAxiomConfig>(
8080
'axiom',
8181
AXIOM_FIELDS,
8282
overrides as Partial<ResolvedAxiomConfig>,

packages/evlog/src/adapters/better-stack.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ export function toBetterStackEvent(event: WideEvent): Record<string, unknown> {
5454
export function createBetterStackDrain(overrides?: Partial<BetterStackConfig>) {
5555
return defineDrain<BetterStackConfig>({
5656
name: 'better-stack',
57-
resolve: () => {
58-
const config = resolveAdapterConfig<BetterStackConfig>('betterStack', BETTER_STACK_FIELDS, overrides)
57+
resolve: async () => {
58+
const config = await resolveAdapterConfig<BetterStackConfig>('betterStack', BETTER_STACK_FIELDS, overrides)
5959
if (!config.sourceToken) {
6060
console.error('[evlog/better-stack] Missing source token. Set NUXT_BETTER_STACK_SOURCE_TOKEN env var or pass to createBetterStackDrain()')
6161
return null

packages/evlog/src/adapters/hyperdx.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ export function toHyperDXOTLPConfig(config: HyperDXConfig): OTLPConfig {
8181
export function createHyperDXDrain(overrides?: Partial<HyperDXConfig>) {
8282
return defineDrain<HyperDXConfig>({
8383
name: 'hyperdx',
84-
resolve: () => {
85-
const config = resolveAdapterConfig<HyperDXConfig>('hyperdx', HYPERDX_FIELDS, overrides)
84+
resolve: async () => {
85+
const config = await resolveAdapterConfig<HyperDXConfig>('hyperdx', HYPERDX_FIELDS, overrides)
8686
if (!config.apiKey) {
8787
console.error('[evlog/hyperdx] Missing apiKey. Set HYPERDX_API_KEY or NUXT_HYPERDX_API_KEY, or pass to createHyperDXDrain()')
8888
return null

packages/evlog/src/adapters/otlp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,8 @@ function getHeadersFromEnv(): Record<string, string> | undefined {
245245
export function createOTLPDrain(overrides?: Partial<OTLPConfig>) {
246246
return defineDrain<OTLPConfig>({
247247
name: 'otlp',
248-
resolve: () => {
249-
const config = resolveAdapterConfig<OTLPConfig>('otlp', OTLP_FIELDS, overrides)
248+
resolve: async () => {
249+
const config = await resolveAdapterConfig<OTLPConfig>('otlp', OTLP_FIELDS, overrides)
250250

251251
// OTLP-specific: resolve headers from env if not provided via config
252252
if (!config.headers) {

packages/evlog/src/adapters/posthog.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ export function toPostHogEvent(event: WideEvent, config: PostHogEventsConfig): P
105105
export function createPostHogDrain(overrides?: Partial<PostHogConfig>) {
106106
return defineDrain<PostHogConfig>({
107107
name: 'posthog',
108-
resolve: () => {
109-
const config = resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)
108+
resolve: async () => {
109+
const config = await resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)
110110
if (!config.apiKey) {
111111
console.error('[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogDrain()')
112112
return null
@@ -172,8 +172,8 @@ export async function sendBatchToPostHog(events: WideEvent[], config: PostHogCon
172172
export function createPostHogEventsDrain(overrides?: Partial<PostHogEventsConfig>) {
173173
return defineDrain<PostHogEventsConfig>({
174174
name: 'posthog-events',
175-
resolve: () => {
176-
const config = resolveAdapterConfig<PostHogEventsConfig>('posthog', POSTHOG_EVENTS_FIELDS, overrides)
175+
resolve: async () => {
176+
const config = await resolveAdapterConfig<PostHogEventsConfig>('posthog', POSTHOG_EVENTS_FIELDS, overrides)
177177
if (!config.apiKey) {
178178
console.error('[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogEventsDrain()')
179179
return null

packages/evlog/src/adapters/sentry.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ function buildEnvelopeBody(logs: SentryLog[], dsn: string): string {
224224
export function createSentryDrain(overrides?: Partial<SentryConfig>) {
225225
return defineDrain<SentryConfig>({
226226
name: 'sentry',
227-
resolve: () => {
228-
const config = resolveAdapterConfig<SentryConfig>('sentry', SENTRY_FIELDS, overrides)
227+
resolve: async () => {
228+
const config = await resolveAdapterConfig<SentryConfig>('sentry', SENTRY_FIELDS, overrides)
229229
if (!config.dsn) {
230230
console.error('[evlog/sentry] Missing DSN. Set NUXT_SENTRY_DSN/SENTRY_DSN env var or pass to createSentryDrain()')
231231
return null

0 commit comments

Comments
 (0)