Skip to content

@zenstackhq/server 3.6.x bundles h3 types into nuxt.d.mts, breaking consumers on newer h3 #2601

@genu

Description

@genu

Symptom

In 3.6.x, consuming @zenstackhq/server/nuxt from a project on a newer h3 (e.g. Nuxt 4 uses h3@1.15.9) produces a TS2345:

Argument of type 'H3Event<EventHandlerRequest>' is not assignable to parameter of type 'H3Event<EventHandlerRequest>'.
Type is missing the following properties: fetch, $fetch, waitUntil, captureError

Two H3Event types with the same name, structurally incompatible. Worked in 3.5.6.

Root cause

The published packages/server/dist/nuxt.d.mts inlines H3Event (and its transitive graph) from whatever h3 version sits in zenstack's own node_modules at build time — currently 1.15.5. So downstream projects on newer h3 end up with a stale structural copy.

The source is fine — packages/server/src/adapter/nuxt/handler.ts does import { type H3Event } from 'h3'. The issue is the build pipeline.

What changed in 3.6.0

Build tool was swapped from tsuptsdown. tsup emitted declarations via tsc, preserving import ... from 'h3'. tsdown (via rolldown-plugin-dts) bundles declarations by default, and with no external list / no dts.resolve: false, it inlines types from any dep that isn't marked external.

Why h3 isn't external

packages/server/package.json lists h3 only as a devDependency, and the shared packages/config/tsdown-config/src/index.ts doesn't add it to external. Same likely applies to other adapter deps (nuxt, next, express, fastify, hono, elysia, @sveltejs/kit) — worth checking.

Suggested fix

One of:

  • Add h3 (and the other adapter deps) to peerDependencies in packages/server/package.json, so tsdown treats them as external.
  • Or mark them external explicitly in the server's tsdown.config.ts.
  • Or disable dts bundling (dts: { resolve: false }) for this package.

Repro

Nuxt 4 project with @zenstackhq/server@3.6.1 + h3@1.15.9; pass the handler's event into any function typed with the real H3Event from h3. Downgrading to 3.5.6 or narrowing the consumer's param type to { headers: Headers } avoids the error.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions