-
-
Notifications
You must be signed in to change notification settings - Fork 65
Description
What version of Elysia is running?
1.4.11
What platform is your computer?
Darwin 25.0.0 arm64 arm
What steps can reproduce the bug?
Reproduction repository: https://github.com/alycoy/eden-treaty-type-loss
-
Clone the reproduction repository
-
Install dependencies:
bun install -
The monorepo has
backend/andclient/packages with TypeScript path aliases in roottsconfig.json:{ "compilerOptions": { "baseUrl": ".", "paths": { "@backend": ["./backend/src/index.ts"] } } } -
Create a Better Auth plugin with
.mount()and.macro():// backend/src/http/plugins/better-auth.ts export const betterAuthPlugin = new Elysia({ name: "better-auth" }) .mount(auth.handler) .macro({ auth: { async resolve({ status, request: { headers } }) { const session = await auth.api.getSession({ headers }); if (!session) { return status(401, { message: "Unauthorized" }); } return { user: session.user, session: session.session }; }, }, });
-
Use the plugin inside a module (not at the root app level):
// backend/src/modules/dashboard/index.ts export const dashboardModule = new Elysia({ prefix: "/api" }) .use(betterAuthPlugin) // Using plugin inside module .get("/dashboard", async ({ user }) => { return { example: "Help me Elysia!" }; }, { auth: true, response: { 200: t.Object({ example: t.String() }) } });
-
Import the module in the main app and export the type:
// backend/src/index.ts const app = new Elysia() .get("/", () => "Hello Elysia") .use(dashboardModule) .listen(3000); export type App = typeof app;
-
Import and use the type in the client package:
// client/src/api.ts import { treaty } from "@elysiajs/eden"; import type { App } from "@backend"; export const api = treaty<App>("localhost:3000"); const { data } = await api.broken.get(); // This should error, but doesn't because data is 'any': data.thisPropertyDoesNotExist?.().whatever[123]();
-
Run
bunx tsc --noEmitin the client folder - no TypeScript errors -
Hover over
datain your IDE - it showsanyinstead of the expected type
What is the expected behavior?
Eden Treaty should maintain full type inference when importing the App type across packages. The data variable should be properly typed with the response schema defined in the backend route ({ example: string }), and TypeScript should catch type errors when trying to access non-existent properties.
This works correctly when:
- The
betterAuthPluginis used at the root app level (not inside a module) - The type is used within the same package (backend)
What do you see instead?
When betterAuthPlugin is used inside a module that gets imported into the main app, Eden Treaty loses all type inference for cross-package imports. The data variable becomes any, and TypeScript fails to catch obvious type errors.
Proof:
bunx tsc --noEmitshows no errors even with nonsensical code likedata.thisPropertyDoesNotExist?.().whatever[123]()- IDE hover shows
data: anyinstead of the proper response type - Commenting out
.use(betterAuthPlugin)from the dashboard module immediately restores full type inference
Additional information
Key Finding: This ONLY happens when the plugin is used inside a module. Using the exact same betterAuthPlugin at the root app level works fine with perfect type inference.
What I've tried:
- Using the plugin only at root level → Works, but then I lose type inference for
userin the module - Creating separate plugin instances → Same issue persists
- Manual type assertions → Defeats the purpose of Eden Treaty's end-to-end type safety
Reproduction: The issue is 100% reproducible by simply commenting/uncommenting the .use(betterAuthPlugin) line in the dashboard module and observing the type inference change.
This makes it impossible to use Better Auth (or any complex plugin with .mount() + .macro()) in a modular monorepo architecture while maintaining end-to-end type safety with Eden Treaty.
Have you try removing the node_modules and bun.lockb and try again yet?
Yes