Skip to content

Eden Treaty loses type inference with Better Auth plugin in modules #215

@alycoy

Description

@alycoy

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

  1. Clone the reproduction repository

  2. Install dependencies: bun install

  3. The monorepo has backend/ and client/ packages with TypeScript path aliases in root tsconfig.json:

    {
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "@backend": ["./backend/src/index.ts"]
        }
      }
    }
  4. 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 };
          },
        },
      });
  5. 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() })
        }
      });
  6. 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;
  7. 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]();
  8. Run bunx tsc --noEmit in the client folder - no TypeScript errors

  9. Hover over data in your IDE - it shows any instead 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 betterAuthPlugin is 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 --noEmit shows no errors even with nonsensical code like data.thisPropertyDoesNotExist?.().whatever[123]()
  • IDE hover shows data: any instead 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 user in 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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinggood first issueGood for newcomers

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions