@@ -401,9 +401,57 @@ export default class Elysia<
401401 }
402402 }
403403
404+ // Normalize any remaining string references in the final result
405+ if ( typeof merged . body === 'string' ) {
406+ merged . body = this . normalizeSchemaReference ( merged . body )
407+ }
408+ if ( typeof merged . headers === 'string' ) {
409+ merged . headers = this . normalizeSchemaReference ( merged . headers )
410+ }
411+ if ( typeof merged . query === 'string' ) {
412+ merged . query = this . normalizeSchemaReference ( merged . query )
413+ }
414+ if ( typeof merged . params === 'string' ) {
415+ merged . params = this . normalizeSchemaReference ( merged . params )
416+ }
417+ if ( typeof merged . cookie === 'string' ) {
418+ merged . cookie = this . normalizeSchemaReference ( merged . cookie )
419+ }
420+ if ( merged . response && typeof merged . response !== 'string' ) {
421+ // Normalize string references in status code objects
422+ const response = merged . response as any
423+ if ( 'type' in response || '$ref' in response ) {
424+ // It's a schema, not a status code object
425+ if ( typeof response === 'string' ) {
426+ merged . response = this . normalizeSchemaReference ( response )
427+ }
428+ } else {
429+ // It's a status code object, normalize each value
430+ for ( const [ status , schema ] of Object . entries ( response ) ) {
431+ if ( typeof schema === 'string' ) {
432+ response [ status ] = this . normalizeSchemaReference ( schema )
433+ }
434+ }
435+ }
436+ }
437+
404438 return merged
405439 }
406440
441+ /**
442+ * Normalize string schema references to TRef nodes for proper merging
443+ */
444+ private normalizeSchemaReference (
445+ schema : TSchema | string | undefined
446+ ) : TSchema | undefined {
447+ if ( ! schema ) return undefined
448+ if ( typeof schema !== 'string' ) return schema
449+
450+ // Convert string reference to t.Ref node
451+ // This allows string aliases to participate in schema composition
452+ return t . Ref ( schema )
453+ }
454+
407455 /**
408456 * Merge two schema properties (body, query, headers, params, cookie)
409457 */
@@ -414,15 +462,17 @@ export default class Elysia<
414462 if ( ! existing ) return incoming
415463 if ( ! incoming ) return existing
416464
417- // If either is a string reference, we can't merge - use incoming
418- if ( typeof existing === 'string' || typeof incoming === 'string' ) {
419- return incoming
420- }
465+ // Normalize string references to TRef nodes so they can be merged
466+ const existingSchema = this . normalizeSchemaReference ( existing )
467+ const incomingSchema = this . normalizeSchemaReference ( incoming )
468+
469+ if ( ! existingSchema ) return incoming
470+ if ( ! incomingSchema ) return existing
421471
422472 // If both are object schemas, merge them
423473 const { schema : mergedSchema , notObjects } = mergeObjectSchemas ( [
424- existing ,
425- incoming
474+ existingSchema ,
475+ incomingSchema
426476 ] )
427477
428478 // If we have non-object schemas, create an Intersect
@@ -458,45 +508,51 @@ export default class Elysia<
458508 if ( ! existing ) return incoming
459509 if ( ! incoming ) return existing
460510
461- // If either is a string, we can't merge - use incoming
462- if ( typeof existing === 'string' || typeof incoming === 'string' ) {
463- return incoming
464- }
511+ // Normalize string references to TRef nodes
512+ const normalizedExisting = typeof existing === 'string'
513+ ? this . normalizeSchemaReference ( existing )
514+ : existing
515+ const normalizedIncoming = typeof incoming === 'string'
516+ ? this . normalizeSchemaReference ( incoming )
517+ : incoming
518+
519+ if ( ! normalizedExisting ) return incoming
520+ if ( ! normalizedIncoming ) return existing
465521
466- // Check if either is a TSchema (has 'type' property) vs status code object
467- const existingIsSchema = 'type' in existing
468- const incomingIsSchema = 'type' in incoming
522+ // Check if either is a TSchema (has 'type' or '$ref' property) vs status code object
523+ const existingIsSchema = 'type' in normalizedExisting || '$ref' in normalizedExisting
524+ const incomingIsSchema = 'type' in normalizedIncoming || '$ref' in normalizedIncoming
469525
470526 // If both are plain schemas, preserve existing (route-specific schema takes precedence)
471527 if ( existingIsSchema && incomingIsSchema ) {
472- return existing
528+ return normalizedExisting
473529 }
474530
475531 // If existing is status code object and incoming is plain schema,
476532 // merge incoming as status 200 to preserve other status codes
477533 if ( ! existingIsSchema && incomingIsSchema ) {
478- return ( existing as Record < number , TSchema | string > ) [ 200 ] ===
534+ return ( normalizedExisting as Record < number , TSchema | string > ) [ 200 ] ===
479535 undefined
480536 ? {
481- ...existing ,
482- 200 : incoming
537+ ...normalizedExisting ,
538+ 200 : normalizedIncoming
483539 }
484- : existing
540+ : normalizedExisting
485541 }
486542
487543 // If existing is plain schema and incoming is status code object,
488544 // merge existing as status 200 into incoming (spread incoming first to preserve all status codes)
489545 if ( existingIsSchema && ! incomingIsSchema ) {
490546 return {
491- ...incoming ,
492- 200 : existing
547+ ...normalizedIncoming ,
548+ 200 : normalizedExisting
493549 }
494550 }
495551
496552 // Both are status code objects, merge them
497553 return {
498- ...incoming ,
499- ...existing
554+ ...normalizedIncoming ,
555+ ...normalizedExisting
500556 }
501557 }
502558
0 commit comments