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
124 changes: 106 additions & 18 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ import type {
DocumentDecoration,
AfterHandler,
NonResolvableMacroKey,
DiscriminatedMacroEntry,
StandardSchemaV1Like,
ElysiaHandlerToResponseSchema,
ElysiaHandlerToResponseSchemas,
Expand Down Expand Up @@ -5303,8 +5304,7 @@ export default class Elysia<
resolve: Partial<
Ephemeral['resolve'] & Volatile['resolve']
> &
// @ts-ignore
MacroContext['resolve']
(MacroContext & { resolve: {} })['resolve']
},
Definitions['error']
>
Expand Down Expand Up @@ -5335,9 +5335,77 @@ export default class Elysia<
Volatile
>

// Discriminated overload: precise macro context based on activated flags
// Only matches when prior macros exist (non-empty macroFn)
macro<
const Entry extends Record<
string,
DiscriminatedMacroEntry<
Metadata['macroFn'],
IntersectIfObjectSchema<
MergeSchema<
UnwrapRoute<
Metadata['macro'] &
InputSchema<
keyof Definitions['typebox'] & string
>,
Definitions['typebox'],
BasePath
>,
MergeSchema<
Volatile['schema'],
MergeSchema<
Ephemeral['schema'],
Metadata['schema']
>
>
>,
Metadata['standaloneSchema'] &
Ephemeral['standaloneSchema'] &
Volatile['standaloneSchema']
>,
Singleton & {
derive: Partial<
Ephemeral['derive'] & Volatile['derive']
>
resolve: Partial<
Ephemeral['resolve'] & Volatile['resolve']
>
},
Definitions['error'],
Definitions['typebox']
>
>,
const NewMacro extends Entry
>(
macro: Entry
): Elysia<
BasePath,
Singleton,
Definitions,
{
schema: Metadata['schema']
standaloneSchema: Metadata['standaloneSchema']
macro: Metadata['macro'] & Partial<MacroToProperty<NewMacro>>
macroFn: Metadata['macroFn'] & NewMacro
parser: Metadata['parser']
response: Metadata['response']
},
Routes,
Ephemeral,
Volatile
>

macro<
const Input extends Metadata['macro'] &
InputSchema<keyof Definitions['typebox'] & string>,
const MacroContext extends {} extends Metadata['macroFn']
? {}
: MacroToContext<
Metadata['macroFn'],
Record<keyof Metadata['macroFn'], true>,
Definitions['typebox']
>,
const NewMacro extends Macro<
Metadata['macro'] &
InputSchema<keyof Definitions['typebox'] & string>,
Expand All @@ -5353,10 +5421,12 @@ export default class Elysia<
Metadata['standaloneSchema'] &
Ephemeral['standaloneSchema'] &
Volatile['standaloneSchema']
>,
> &
MacroContext,
Singleton & {
derive: Partial<Ephemeral['derive'] & Volatile['derive']>
resolve: Partial<Ephemeral['resolve'] & Volatile['resolve']>
resolve: Partial<Ephemeral['resolve'] & Volatile['resolve']> &
(MacroContext & { resolve: {} })['resolve']
},
Definitions['error']
>
Expand All @@ -5382,6 +5452,13 @@ export default class Elysia<
macro<
const Input extends Metadata['macro'] &
InputSchema<keyof Definitions['typebox'] & string>,
const MacroContext extends {} extends Metadata['macroFn']
? {}
: MacroToContext<
Metadata['macroFn'],
Record<keyof Metadata['macroFn'], true>,
Definitions['typebox']
>,
const NewMacro extends MaybeFunction<
Macro<
Input,
Expand All @@ -5397,10 +5474,12 @@ export default class Elysia<
Metadata['standaloneSchema'] &
Ephemeral['standaloneSchema'] &
Volatile['standaloneSchema']
>,
> &
MacroContext,
Singleton & {
derive: Partial<Ephemeral['derive'] & Volatile['derive']>
resolve: Partial<Ephemeral['resolve'] & Volatile['resolve']>
resolve: Partial<Ephemeral['resolve'] & Volatile['resolve']> &
(MacroContext & { resolve: {} })['resolve']
},
Definitions['error']
>
Expand Down Expand Up @@ -5469,9 +5548,29 @@ export default class Elysia<

applied[seed] = true

for (let [k, value] of Object.entries(macroHook)) {
const entries = Object.entries(macroHook)

for (const [k, value] of entries) {
if (k === 'seed') continue

if (k in macro) {
this.applyMacro(
localHook,
{ [k]: value },
{ applied, iteration: iteration + 1 }
)

// Remove the raw macro flag key (e.g. `auth`) from localHook now
// that its dependency has been recursively expanded. This prevents
// the flag from leaking as an unrecognised property into the final hook.
delete localHook[key]
}
}

for (let [k, value] of entries) {
if (k === 'seed') continue
if (k in macro) continue

if (k in emptySchema) {
insertStandaloneValidator(
localHook,
Expand Down Expand Up @@ -5499,17 +5598,6 @@ export default class Elysia<
continue
}

if (k in macro) {
this.applyMacro(
localHook,
{ [k]: value },
{ applied, iteration: iteration + 1 }
)

delete localHook[key]
continue
}

if (
(k === 'derive' || k === 'resolve') &&
typeof value === 'function'
Expand Down
84 changes: 84 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1789,6 +1789,90 @@ export type NonResolvableMacroKey =
| keyof InputSchema
| 'resolve'

/**
* Build a discriminated union of macro entry shapes.
* Each union member correlates specific flag values (e.g., `sessions: true`)
* with the corresponding callback context. TypeScript's union narrowing
* picks the correct member based on actual flag values in the object literal.
*
* Generates: no-flags entry | single-flag entries | all-flags entry
*/
export type DiscriminatedMacroEntry<
MacroFn extends Macro,
TypedRoute extends RouteSchema = {},
Singleton extends SingletonBase = {
decorator: {}
store: {}
derive: {}
resolve: {}
},
Errors extends Record<string, Error> = {},
Definitions extends DefinitionBase['typebox'] = {}
> = {} extends MacroFn
? MacroProperty<{}, TypedRoute, Singleton, Errors> &
Record<string, any> & { call?: never; bind?: never }
:
| MacroEntryForSelected<MacroFn, never, TypedRoute, Singleton, Errors, Definitions>
| SingleKeyMacroEntries<MacroFn, TypedRoute, Singleton, Errors, Definitions>
| MacroEntryForSelected<
MacroFn,
keyof MacroFn & string,
TypedRoute,
Singleton,
Errors,
Definitions
>

type MacroEntryForSelected<
MacroFn extends Macro,
Selected extends keyof MacroFn,
TypedRoute extends RouteSchema = {},
Singleton extends SingletonBase = {
decorator: {}
store: {}
derive: {}
resolve: {}
},
Errors extends Record<string, Error> = {},
Definitions extends DefinitionBase['typebox'] = {}
> = {
[K in Selected]: true
} & {
[K in Exclude<keyof MacroFn, Selected>]?: false
} & { call?: never; bind?: never } & MacroProperty<
{},
TypedRoute &
MacroToContext<
MacroFn,
{ [K in Selected]: true },
Definitions
>,
Singleton & {
resolve: (MacroToContext<
MacroFn,
{ [K in Selected]: true },
Definitions
> & { resolve: {} })['resolve']
},
Errors
>

type SingleKeyMacroEntries<
MacroFn extends Macro,
TypedRoute extends RouteSchema = {},
Singleton extends SingletonBase = {
decorator: {}
store: {}
derive: {}
resolve: {}
},
Errors extends Record<string, Error> = {},
Definitions extends DefinitionBase['typebox'] = {},
K extends keyof MacroFn = keyof MacroFn
> = K extends any
? MacroEntryForSelected<MacroFn, K & string, TypedRoute, Singleton, Errors, Definitions>
: never

interface RouteSchemaWithResolvedMacro extends RouteSchema {
response: PossibleResponse
return: PossibleResponse
Expand Down
Loading
Loading