Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions src/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,18 +402,37 @@ const hasReturn = (v: string | HookContainer<any> | Function) => {
? v.fn.toString()
: typeof v === 'string'
? v.toString()
: v
: v.toString()

const parenthesisEnd = fnLiteral.indexOf(')')

// Is direct arrow function return eg. () => 1
if (
fnLiteral.charCodeAt(parenthesisEnd + 2) === 61 &&
fnLiteral.charCodeAt(parenthesisEnd + 5) !== 123
) {
if (isObject) v.hasReturn = true
// Check for arrow function expression (direct return without braces)
// Handle both `) => x` and `)=>x` formats (with or without spaces)
const arrowIndex = fnLiteral.indexOf('=>', parenthesisEnd)

if (arrowIndex !== -1) {
// Skip any whitespace after `=>` (space, tab, newline, carriage return)
let afterArrow = arrowIndex + 2
let charCode: number
while (
afterArrow < fnLiteral.length &&
((charCode = fnLiteral.charCodeAt(afterArrow)) === 32 || // space
charCode === 9 || // tab
charCode === 10 || // newline
charCode === 13) // carriage return
) {
afterArrow++
}

// If the first non-whitespace char after `=>` is not `{`, it's a direct return
if (
afterArrow < fnLiteral.length &&
fnLiteral.charCodeAt(afterArrow) !== 123
) {
if (isObject) v.hasReturn = true

return true
return true
}
}

const result = fnLiteral.includes('return')
Expand Down
143 changes: 143 additions & 0 deletions test/core/before-handle-arrow.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { describe, expect, it } from 'bun:test'
import { Elysia } from '../../src'

describe('beforeHandle with arrow functions', () => {
it('should execute beforeHandle with arrow function expression', async () => {
let beforeHandleCalled = false

const app = new Elysia()
.get('/test', () => 'ok', {
// Arrow function expression (no braces) - this was broken with minified code
beforeHandle: () => {
beforeHandleCalled = true
}
})

const response = await app.handle(new Request('http://localhost/test'))
expect(response.status).toBe(200)
expect(beforeHandleCalled).toBe(true)
})

it('should execute beforeHandle with arrow function returning value', async () => {
const app = new Elysia()
.get('/test', () => 'should not reach', {
// Arrow function expression that returns early
beforeHandle: () => 'intercepted'
})

const response = await app.handle(new Request('http://localhost/test'))
expect(await response.text()).toBe('intercepted')
})

it('should execute async beforeHandle with arrow function expression', async () => {
let beforeHandleCalled = false

const app = new Elysia()
.get('/test', () => 'ok', {
// Async arrow function expression
beforeHandle: async () => {
beforeHandleCalled = true
}
})

const response = await app.handle(new Request('http://localhost/test'))
expect(response.status).toBe(200)
expect(beforeHandleCalled).toBe(true)
})

it('should execute beforeHandle with complex arrow expression', async () => {
let validatorCalled = false
const validator = () => {
validatorCalled = true
}

const app = new Elysia()
.get('/test', () => 'ok', {
// Complex arrow expression like: async ({status}) => requireSignature()({status})
// This pattern was broken when code is minified
beforeHandle: () => validator()
})

const response = await app.handle(new Request('http://localhost/test'))
expect(response.status).toBe(200)
expect(validatorCalled).toBe(true)
})

it('should execute beforeHandle with arrow function block', async () => {
let beforeHandleCalled = false

const app = new Elysia()
.get('/test', () => 'ok', {
// Arrow function with block (this always worked)
beforeHandle: () => {
beforeHandleCalled = true
return
}
})

const response = await app.handle(new Request('http://localhost/test'))
expect(response.status).toBe(200)
expect(beforeHandleCalled).toBe(true)
})

it('should handle multiple beforeHandle hooks with arrow expressions', async () => {
const callOrder: number[] = []

const app = new Elysia()
.get('/test', () => 'ok', {
beforeHandle: [
() => {
callOrder.push(1)
},
() => {
callOrder.push(2)
},
() => {
callOrder.push(3)
}
]
})

const response = await app.handle(new Request('http://localhost/test'))
expect(response.status).toBe(200)
expect(callOrder).toEqual([1, 2, 3])
})

// Test with truly minified code (no spaces around arrow)
// This simulates what happens when code is bundled and minified
it('should execute beforeHandle with truly minified arrow function (no spaces)', async () => {
// Construct a function with no spaces: async()=>'intercepted'
// This is what real minifiers produce
const minifiedHandler = Function("return async()=>'intercepted'")()

const app = new Elysia().get('/test', () => 'should not reach', {
beforeHandle: minifiedHandler
})

const response = await app.handle(new Request('http://localhost/test'))
expect(await response.text()).toBe('intercepted')
})

// Test with actual HTTP server (not just app.handle)
it('should work with live server', async () => {
let beforeHandleCalled = false

const app = new Elysia()
.get('/test', () => 'ok', {
beforeHandle: () => {
beforeHandleCalled = true
}
})
.listen(0)

try {
const response = await fetch(
`http://localhost:${app.server?.port}/test`
)
expect(response.status).toBe(200)
expect(beforeHandleCalled).toBe(true)
} finally {
await app.stop()
}
})
})