|
| 1 | +/** |
| 2 | + * How evlog reads Nitro runtime config from **published** ESM. |
| 3 | + * |
| 4 | + * **Why not** `import('nitro/runtime-config')` as a string literal in source? |
| 5 | + * Those subpaths are virtual or specially resolved. App Rollup can resolve them |
| 6 | + * for first-party code; for dependency chunks (`node_modules/evlog/dist/...`), |
| 7 | + * strict presets (e.g. `cloudflare-durable`) may fail with “externals are not |
| 8 | + * allowed”. A literal dynamic import is enough for Rollup to pre-resolve. |
| 9 | + * |
| 10 | + * **Strategy** |
| 11 | + * |
| 12 | + * 1. `process.env.__EVLOG_CONFIG` — JSON set by evlog Nitro modules (no virtual |
| 13 | + * modules; preferred in production Workers builds). |
| 14 | + * 2. Computed module IDs — `['a','b'].join('/')` passed to `import()` so emitted |
| 15 | + * JS does not contain a static `import("a/b")`. |
| 16 | + * 3. Plugin resolution tries Nitro v3 first, then nitropack internal config (v2). |
| 17 | + * 4. Adapter resolution keeps historical order: nitropack runtime barrel, then v3. |
| 18 | + * |
| 19 | + * Not exported from `evlog/toolkit` — package-internal only. |
| 20 | + */ |
| 21 | + |
| 22 | +import type { EvlogConfig } from '../nitro' |
| 23 | + |
| 24 | +const EVLOG_NITRO_ENV = '__EVLOG_CONFIG' as const |
| 25 | + |
| 26 | +type NitroRuntimeConfigModule = { |
| 27 | + useRuntimeConfig: () => Record<string, any> |
| 28 | +} |
| 29 | + |
| 30 | +function nitroV3RuntimeConfigSpecifier(): string { |
| 31 | + return ['nitro', 'runtime-config'].join('/') |
| 32 | +} |
| 33 | + |
| 34 | +function nitropackRuntimeSpecifier(): string { |
| 35 | + return ['nitropack', 'runtime'].join('/') |
| 36 | +} |
| 37 | + |
| 38 | +function nitropackInternalRuntimeConfigSpecifier(): string { |
| 39 | + return ['nitropack', 'runtime', 'internal', 'config'].join('/') |
| 40 | +} |
| 41 | + |
| 42 | +async function importOrNull(specifier: string): Promise<unknown> { |
| 43 | + try { |
| 44 | + return await import(specifier) |
| 45 | + } catch { |
| 46 | + return null |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +function isRuntimeConfigModule(mod: unknown): mod is NitroRuntimeConfigModule { |
| 51 | + return ( |
| 52 | + typeof mod === 'object' |
| 53 | + && mod !== null |
| 54 | + && 'useRuntimeConfig' in mod |
| 55 | + && typeof (mod as NitroRuntimeConfigModule).useRuntimeConfig === 'function' |
| 56 | + ) |
| 57 | +} |
| 58 | + |
| 59 | +/** Snapshot from env, or `undefined` if unset / invalid JSON. */ |
| 60 | +export function readEvlogConfigFromNitroEnv(): EvlogConfig | undefined { |
| 61 | + const raw = process.env[EVLOG_NITRO_ENV] |
| 62 | + if (raw === undefined || raw === '') return undefined |
| 63 | + try { |
| 64 | + return JSON.parse(raw) as EvlogConfig |
| 65 | + } catch { |
| 66 | + return undefined |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +let cachedNitropackRuntime: NitroRuntimeConfigModule | null | undefined |
| 71 | +let cachedNitroV3Runtime: NitroRuntimeConfigModule | null | undefined |
| 72 | +let cachedNitropackInternalConfig: NitroRuntimeConfigModule | null | undefined |
| 73 | + |
| 74 | +async function getNitropackRuntime(): Promise<NitroRuntimeConfigModule | null> { |
| 75 | + if (cachedNitropackRuntime !== undefined) return cachedNitropackRuntime |
| 76 | + const mod = await importOrNull(nitropackRuntimeSpecifier()) |
| 77 | + cachedNitropackRuntime = isRuntimeConfigModule(mod) ? mod : null |
| 78 | + return cachedNitropackRuntime |
| 79 | +} |
| 80 | + |
| 81 | +async function getNitroV3Runtime(): Promise<NitroRuntimeConfigModule | null> { |
| 82 | + if (cachedNitroV3Runtime !== undefined) return cachedNitroV3Runtime |
| 83 | + const mod = await importOrNull(nitroV3RuntimeConfigSpecifier()) |
| 84 | + cachedNitroV3Runtime = isRuntimeConfigModule(mod) ? mod : null |
| 85 | + return cachedNitroV3Runtime |
| 86 | +} |
| 87 | + |
| 88 | +async function getNitropackInternalRuntimeConfig(): Promise<NitroRuntimeConfigModule | null> { |
| 89 | + if (cachedNitropackInternalConfig !== undefined) return cachedNitropackInternalConfig |
| 90 | + const mod = await importOrNull(nitropackInternalRuntimeConfigSpecifier()) |
| 91 | + cachedNitropackInternalConfig = isRuntimeConfigModule(mod) ? mod : null |
| 92 | + return cachedNitropackInternalConfig |
| 93 | +} |
| 94 | + |
| 95 | +function evlogSlice(config: Record<string, any>): EvlogConfig | undefined { |
| 96 | + const { evlog } = config |
| 97 | + if (evlog && typeof evlog === 'object') return evlog as EvlogConfig |
| 98 | + return undefined |
| 99 | +} |
| 100 | + |
| 101 | +/** |
| 102 | + * Options for evlog Nitro plugins (nitropack v2 and Nitro v3). |
| 103 | + * Env bridge first; then Nitro v3 `runtime-config`; then nitropack internal config. |
| 104 | + */ |
| 105 | +export async function resolveEvlogConfigForNitroPlugin(): Promise<EvlogConfig | undefined> { |
| 106 | + const fromEnv = readEvlogConfigFromNitroEnv() |
| 107 | + if (fromEnv !== undefined) return fromEnv |
| 108 | + |
| 109 | + const v3 = await getNitroV3Runtime() |
| 110 | + if (v3) { |
| 111 | + const slice = evlogSlice(v3.useRuntimeConfig()) |
| 112 | + if (slice !== undefined) return slice |
| 113 | + } |
| 114 | + |
| 115 | + const internal = await getNitropackInternalRuntimeConfig() |
| 116 | + if (internal) { |
| 117 | + const slice = evlogSlice(internal.useRuntimeConfig()) |
| 118 | + if (slice !== undefined) return slice |
| 119 | + } |
| 120 | + |
| 121 | + return undefined |
| 122 | +} |
| 123 | + |
| 124 | +/** |
| 125 | + * Full `useRuntimeConfig()` object for drain adapters (nitropack first, then v3). |
| 126 | + */ |
| 127 | +export async function getNitroRuntimeConfigRecord(): Promise<Record<string, any> | undefined> { |
| 128 | + const nitropack = await getNitropackRuntime() |
| 129 | + if (nitropack) return nitropack.useRuntimeConfig() |
| 130 | + |
| 131 | + const v3 = await getNitroV3Runtime() |
| 132 | + if (v3) return v3.useRuntimeConfig() |
| 133 | + |
| 134 | + return undefined |
| 135 | +} |
0 commit comments