Skip to content

Commit be88337

Browse files
authored
Merge pull request #1691 from raunak-rpm/fix/macro-function-inference-1574
fix: named macros with function syntax now infer previous macro resolve types (#1574)
2 parents 370a6ac + 37cfac1 commit be88337

File tree

2 files changed

+155
-1
lines changed

2 files changed

+155
-1
lines changed

src/index.ts

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5261,6 +5261,126 @@ export default class Elysia<
52615261
return this
52625262
}
52635263

5264+
// Overload 1: Named macro with function syntax (fixes issue #1574)
5265+
// This overload handles: .macro("name", (param) => ({ resolve: ... }))
5266+
// For function syntax, we compute MacroContext from ALL previous macros
5267+
// because we can't infer which macros are selected before the function returns
5268+
macro<
5269+
const Name extends string,
5270+
// Compute MacroContext from all previous macros, assuming all are enabled
5271+
const MacroContext extends {} extends Metadata['macroFn']
5272+
? {}
5273+
: MacroToContext<
5274+
Metadata['macroFn'],
5275+
// Use all macro keys set to true to include all possible resolves
5276+
{ [K in keyof Metadata['macroFn']]: true },
5277+
Definitions['typebox']
5278+
>,
5279+
const Param,
5280+
const Property extends Metadata['macro'] &
5281+
MacroProperty<
5282+
Metadata['macro'] &
5283+
InputSchema<keyof Definitions['typebox'] & string> & {
5284+
[name in Name]?: boolean
5285+
},
5286+
MacroContext,
5287+
Singleton & {
5288+
derive: Partial<Ephemeral['derive'] & Volatile['derive']>
5289+
resolve: Partial<
5290+
Ephemeral['resolve'] & Volatile['resolve']
5291+
> &
5292+
// @ts-ignore
5293+
MacroContext['resolve']
5294+
},
5295+
Definitions['error']
5296+
>
5297+
>(
5298+
name: Name,
5299+
macro: (param: Param) => Property
5300+
): Elysia<
5301+
BasePath,
5302+
Singleton,
5303+
Definitions,
5304+
{
5305+
schema: Metadata['schema']
5306+
standaloneSchema: Metadata['standaloneSchema']
5307+
macro: Metadata['macro'] & {
5308+
[name in Name]?: Param
5309+
}
5310+
macroFn: Metadata['macroFn'] & {
5311+
[name in Name]: (param: Param) => Property
5312+
}
5313+
parser: Metadata['parser']
5314+
response: Metadata['response']
5315+
},
5316+
Routes,
5317+
Ephemeral,
5318+
Volatile
5319+
>
5320+
5321+
// Overload 2: Named macro with object syntax (original)
5322+
// This overload handles: .macro("name", { resolve: ... })
5323+
macro<
5324+
const Name extends string,
5325+
const Input extends Metadata['macro'] &
5326+
InputSchema<keyof Definitions['typebox'] & string>,
5327+
const Schema extends MergeSchema<
5328+
UnwrapRoute<Input, Definitions['typebox'], BasePath>,
5329+
MergeSchema<
5330+
Volatile['schema'],
5331+
MergeSchema<Ephemeral['schema'], Metadata['schema']>
5332+
> &
5333+
Metadata['standaloneSchema'] &
5334+
Ephemeral['standaloneSchema'] &
5335+
Volatile['standaloneSchema']
5336+
>,
5337+
const MacroContext extends {} extends Metadata['macroFn']
5338+
? {}
5339+
: MacroToContext<
5340+
Metadata['macroFn'],
5341+
Omit<Input, NonResolvableMacroKey>,
5342+
Definitions['typebox']
5343+
>,
5344+
const Property extends MacroProperty<
5345+
Metadata['macro'] &
5346+
InputSchema<keyof Definitions['typebox'] & string> & {
5347+
[name in Name]?: boolean
5348+
},
5349+
Schema & MacroContext,
5350+
Singleton & {
5351+
derive: Partial<Ephemeral['derive'] & Volatile['derive']>
5352+
resolve: Partial<
5353+
Ephemeral['resolve'] & Volatile['resolve']
5354+
> &
5355+
// @ts-ignore
5356+
MacroContext['resolve']
5357+
},
5358+
Definitions['error']
5359+
>
5360+
>(
5361+
name: Name,
5362+
macro: (Input extends any ? Input : Prettify<Input>) & Property
5363+
): Elysia<
5364+
BasePath,
5365+
Singleton,
5366+
Definitions,
5367+
{
5368+
schema: Metadata['schema']
5369+
standaloneSchema: Metadata['standaloneSchema']
5370+
macro: Metadata['macro'] & {
5371+
[name in Name]?: boolean
5372+
}
5373+
macroFn: Metadata['macroFn'] & {
5374+
[name in Name]: Property
5375+
}
5376+
parser: Metadata['parser']
5377+
response: Metadata['response']
5378+
},
5379+
Routes,
5380+
Ephemeral,
5381+
Volatile
5382+
>
5383+
52645384
macro<
52655385
const Name extends string,
52665386
const Input extends Metadata['macro'] &
@@ -5415,7 +5535,10 @@ export default class Elysia<
54155535
Volatile
54165536
>
54175537

5418-
macro(macroOrName: string | Macro, macro?: Macro) {
5538+
macro(
5539+
macroOrName: string | Macro,
5540+
macro?: Macro | ((...args: any[]) => any)
5541+
) {
54195542
if (typeof macroOrName === 'string' && !macro)
54205543
throw new Error('Macro function is required')
54215544

test/macro/macro.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,4 +1438,35 @@ describe('Macro', () => {
14381438

14391439
expect(invalid3.status).toBe(422)
14401440
})
1441+
1442+
// Issue #1574: Named macros with function syntax should infer previous macro resolve types
1443+
it('infer previous macro resolve in function syntax (issue #1574)', async () => {
1444+
const app = new Elysia()
1445+
.macro('auth', {
1446+
resolve: () => ({
1447+
user: 'authenticated-user' as const
1448+
})
1449+
})
1450+
.macro('permission', (permission: string) => ({
1451+
auth: true,
1452+
// The 'user' type should be inferred from the 'auth' macro's resolve
1453+
resolve: ({ user }) => ({
1454+
permission,
1455+
userFromAuth: user
1456+
})
1457+
}))
1458+
.get('/', ({ userFromAuth, permission }) => ({
1459+
user: userFromAuth,
1460+
permission
1461+
}), {
1462+
permission: 'admin'
1463+
})
1464+
1465+
const response = await app.handle(req('/')).then((x) => x.json())
1466+
1467+
expect(response).toEqual({
1468+
user: 'authenticated-user',
1469+
permission: 'admin'
1470+
})
1471+
})
14411472
})

0 commit comments

Comments
 (0)