The gap
src/shared/conformance.ts encodes the behaviours every framework integration must share, and describeStandardHttpMatrix drives them. It covers 10 integrations:
hono · express · elysia · fastify · nestjs · orpc · react-router · sveltekit · next · workers
It does not cover evlog/nuxt, evlog/nitro or evlog/nitro/v3 — arguably the most important ones for this project.
Why it is structural, not an oversight
The mount contract injects the drain as a function:
export type ConformanceMount = (options: BaseEvlogOptions) => ConformanceApp | Promise<ConformanceApp>
That works for anything configured per request. But Nitro receives its drain through the evlog:drain hook inside a built app — and a function cannot cross a build boundary. Driving Nitro through this contract would mean writing the drain into a fixture's source and rebuilding per check, which is far too slow for a per-check mount.
Today those integrations rely on their own fixture-based tests (test/nitro/, test/nitro-v3/) plus direct unit tests of internals like callEnrichAndDrain. Those are real tests — the point is only that they are not the shared contract, so nothing guarantees Nitro and Hono behave identically for include / exclude / routes / enrich ordering.
This matters because the matrix is what caught two real regressions: evlog/workers honouring only cf-ray for the request id (#472), and evlog/next accepting plugins without ever applying them (#474). Nuxt and Nitro get no such safety net.
Possible approach
A second, config-based mount mode:
- the fixture declares a drain that writes to a known sink (a file, or an in-process HTTP endpoint the suite owns)
- the mount returns a
readEvents() alongside fire(), instead of the suite injecting a collector
- checks that need
include / exclude / routes pass them through the fixture's nitro.config.ts / module options rather than as middleware options
That means splitting ConformanceCheck into "drive the request" and "read what was drained", so the same behavioural assertions run against either mount style. Build cost can be amortised by building the fixture once per suite rather than per check.
Related
src/shared/conformance.ts is deliberately not exported from evlog/toolkit today, partly for this reason — publishing it as "the contract every integration satisfies" would be inaccurate while the flagship integrations are outside it. The module header documents the exclusion.
A second blocker for any future public export, worth solving alongside: the suite cannot distinguish "your mount is wrong" from "your integration breaks the contract". A mount that serves a 404 is currently reported as a conformance failure. A preflight sanity check — assert the route answers 200 without evlog mounted — would fix that.
The gap
src/shared/conformance.tsencodes the behaviours every framework integration must share, anddescribeStandardHttpMatrixdrives them. It covers 10 integrations:hono·express·elysia·fastify·nestjs·orpc·react-router·sveltekit·next·workersIt does not cover
evlog/nuxt,evlog/nitroorevlog/nitro/v3— arguably the most important ones for this project.Why it is structural, not an oversight
The mount contract injects the drain as a function:
That works for anything configured per request. But Nitro receives its drain through the
evlog:drainhook inside a built app — and a function cannot cross a build boundary. Driving Nitro through this contract would mean writing the drain into a fixture's source and rebuilding per check, which is far too slow for a per-check mount.Today those integrations rely on their own fixture-based tests (
test/nitro/,test/nitro-v3/) plus direct unit tests of internals likecallEnrichAndDrain. Those are real tests — the point is only that they are not the shared contract, so nothing guarantees Nitro and Hono behave identically forinclude/exclude/routes/enrichordering.This matters because the matrix is what caught two real regressions:
evlog/workershonouring onlycf-rayfor the request id (#472), andevlog/nextacceptingpluginswithout ever applying them (#474). Nuxt and Nitro get no such safety net.Possible approach
A second, config-based mount mode:
readEvents()alongsidefire(), instead of the suite injecting a collectorinclude/exclude/routespass them through the fixture'snitro.config.ts/ module options rather than as middleware optionsThat means splitting
ConformanceCheckinto "drive the request" and "read what was drained", so the same behavioural assertions run against either mount style. Build cost can be amortised by building the fixture once per suite rather than per check.Related
src/shared/conformance.tsis deliberately not exported fromevlog/toolkittoday, partly for this reason — publishing it as "the contract every integration satisfies" would be inaccurate while the flagship integrations are outside it. The module header documents the exclusion.A second blocker for any future public export, worth solving alongside: the suite cannot distinguish "your mount is wrong" from "your integration breaks the contract". A mount that serves a 404 is currently reported as a conformance failure. A preflight sanity check — assert the route answers 200 without evlog mounted — would fix that.