Skip to content

macro.beforeHandle runs after nested plugin resolve hooks despite same-queue documentation #1586

@turisanapo

Description

@turisanapo

What version of Elysia is running?

1.4.16

What platform is your computer?

Darwin 25.1.0 arm64 arm

What environment are you using

1.3.1

Are you using dynamic mode?

No

What steps can reproduce the bug?

import { Elysia } from 'elysia'

// Step 1: Auth service with resolve + macro
const authService = new Elysia({ name: 'auth-service' })
  .resolve(() => {
    console.log('1. authService.resolve')
    return { userId: undefined } // Simulating no auth
  })
  .macro({
    isSignedIn: {
      beforeHandle({ userId }) {
        console.log('3. isSignedIn.beforeHandle')
        if (!userId) throw new Error('Unauthorized')
      },
    },
  })
  .as('scoped')

// Step 2: DB client that requires userId
const dbClient = new Elysia({ name: 'db-client' })
  .resolve((ctx) => {
    console.log('2. dbClient.resolve')
    const userId = (ctx as { userId?: string }).userId
    if (!userId) throw new Error('User ID is required') // ❌ Throws here
    return { db: { userId } }
  })
  .as('scoped')

// Step 3: Feature module using dbClient
const feature = new Elysia({ name: 'feature' })
  .use(dbClient)
  .get('/', ({ db }) => `Hello ${db.userId}`)

// Step 4: Main app
const app = new Elysia()
  .use(authService)
  .group('/v1', { isSignedIn: true }, (app) => app.use(feature))
  .listen(3000)

With the example above:

1/ curl http://localhost:3000/v1/
2/ observe error "User ID is required", expected would be "Unauthorized"

What is the expected behavior?

When a group has a macro that defines a beforeHandle hook, it should run before any resolve hooks from plugins used inside that group, since the guard is registered at the group level (before nested plugins).

What do you see instead?

The nested plugin's resolve runs before the macro's beforeHandle, causing errors before the guard can reject requests.

Additional information

According to the documentation, beforeHandle and resolve belong to the same execution queue and their order depends on plugin registration order. However, when using a macro-defined beforeHandle within a group, nested plugins' resolve hooks execute before the macro's beforeHandle.

Have you try removing the node_modules and bun.lockb and try again yet?

Yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions