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
25 changes: 25 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8297,6 +8297,31 @@ export type {
ExtractErrorFromHandle
} from './types'

/**
* Type-safe factory function for creating Elysia instances.
*
* Unlike `new Elysia(options)` wrapped in a plain function, this
* preserves the **literal** prefix type through TypeScript's `const`
* generic inference — preventing route-type intersections when
* multiple factory-created sub-apps are composed via `.use()`.
*
* @example
* ```typescript
* import { createElysia, t } from 'elysia'
*
* // ✅ Prefix literal '/sessions' is preserved in the type
* const sessionsApp = createElysia({ prefix: '/sessions' })
* .get('/', () => ({ sessions: [] }), {
* response: t.Object({ sessions: t.Array(t.String()) })
* })
* ```
*
* @see https://github.com/elysiajs/elysia/issues/1725
*/
export const createElysia = <const BasePath extends string = ''>(
config?: ElysiaConfig<BasePath>
): Elysia<BasePath> => new Elysia<BasePath>(config)

export { env } from './universal/env'
export { file, ElysiaFile } from './universal/file'
export type { ElysiaAdapter } from './adapter'
Expand Down
49 changes: 49 additions & 0 deletions test/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { expectTypeOf } from 'expect-type'
import {
type Cookie,
createElysia,
Elysia,
file,
form,
Expand Down Expand Up @@ -2969,3 +2970,51 @@ type a = keyof {}
}>()
})
}

// ? createElysia preserves prefix literal type (issue #1725)
// Factory-created sub-apps should NOT cause response type intersection
{
const SessionsListDto = t.Object({
sessions: t.Array(t.Object({ id: t.String(), name: t.String() }))
})

const QSessionsListDto = t.Object({
sessions: t.Array(
t.Object({ sessionId: t.String(), bankName: t.String() })
)
})

function createSessionApp() {
return createElysia({ prefix: '/sessions' }).get(
'/',
() => ({ sessions: [{ id: '1', name: 'S1' }] }),
{ response: SessionsListDto }
)
}

function createQSessionApp() {
return createElysia({ prefix: '/question-sessions' }).get(
'/',
() => ({ sessions: [{ sessionId: 'qs-1', bankName: 'B1' }] }),
{ response: QSessionsListDto }
)
}

const app = new Elysia().group('/api', (app) =>
app.use(createSessionApp()).use(createQSessionApp())
)

type Routes = (typeof app)['~Routes']
type SessionsResponse = Routes['api']['sessions']['get']['response'][200]
type QSessionsResponse =
Routes['api']['question-sessions']['get']['response'][200]

// Each response type should be independent — no intersection
expectTypeOf<SessionsResponse>().toEqualTypeOf<{
sessions: { id: string; name: string }[]
}>()

expectTypeOf<QSessionsResponse>().toEqualTypeOf<{
sessions: { sessionId: string; bankName: string }[]
}>()
}