As a workaround you can try match types
trait Key
object Name extends Key
object Age extends Key
type OptValue[K <: Key] = K match
  case Name.type => Option[String]
  case Age.type  => Option[Int]
def dbImpl[K <: Key](k: K): OptValue[K] = k match
  case _: Name.type => Some("abc")
  case _: Age.type  => None
or
val dbImpl: [K <: Key] => K => OptValue[K] =
  [K <: Key] => (k: K) => k match
    case _: Name.type => Some("abc")
    case _: Age.type  => None
I'm reminding that
type DB = [K <: Key] => K => OptValue[K]
val dbImpl: DB = [K <: Key] => (k: K) => k match
  case _: Name.type => Some("abc")
  case _: Age.type  => None
or
def dbImpl[K <: Key](k: K): OptValue[K] = k match
  case Name => Some("abc")
  case Age  => None
or
type Value[K <: Key] = K match
  case Name.type => String
  case Age.type  => Int
def dbImpl[K <: Key](k: K): Option[Value[K]] = k match
  case _: Name.type => Some("abc")
  case _: Age.type  => None
will not work.
scala 3 map tuple to futures of tuple types and back
Scala 3: typed tuple zipping
Express function of arbitrary arity in vanilla Scala 3
Shapeless3 and annotations
How to get match type to work correctly in Scala 3
Another option is type classes
trait Key
object Name extends Key
object Age extends Key
// type class
trait KeyValue[K <: Key]:
  type Value
  type Out = Option[Value]
  def apply(k: K): Out
object KeyValue:
  type Aux[K <: Key, V] = KeyValue[K] { type Value = V }
  def instance[K <: Key, V](f: K => Option[V]): Aux[K, V] = new KeyValue[K]:
    override type Value = V
    override def apply(k: K): Out = f(k)
  given Aux[Name.type, String] = instance(_ => Some("abc"))
  given Aux[Age.type, Int]     = instance(_ => None)
def dbImpl[K <: Key](k: K)(using kv: KeyValue[K]): kv.Out = kv(k)
One more option is inlining and using scala.compiletime.summonFrom
trait Key:
  type Value
object Name extends Key:
  override type Value = String
object Age extends Key:
  override type Value = Int
inline def dbImpl(k: Key): Option[k.Value] = inline k match
  case Name => summonFrom {
    case _: (String =:= k.Value) => Some("abc")
  }
  case Age => summonFrom {
    case _: (Option[Int] =:= Option[k.Value]) => None: Option[Int]
  }
The easiest is to make Value a type parameter rather than type member
trait Key[Value]
object Name extends Key[String]
object Age extends Key[Int]
def dbImpl[V](k: Key[V]): Option[V] = k match
  case Name => Some("abc")
  case Age  => None
This implementation compiles while
trait Key:
  type Value
object Name extends Key:
  override type Value = String
object Age extends Key:
  override type Value = Int
def dbImpl[V](k: Key {type Value = V}): Option[V] = k match
  case Name => Some("abc")
  case Age  => None
or
trait Key:
  type Value
object Key:
  type Aux[V] = Key { type Value = V }
object Name extends Key:
  override type Value = String
object Age extends Key:
  override type Value = Int
def dbImpl[V](k: Key.Aux[V]): Option[V] = k match
  case Name => Some("abc")
  case Age  => None
or
trait Key:
  type Value
object Name extends Key:
  override type Value = String
object Age extends Key:
  override type Value = Int
def dbImpl(k: Key): Option[k.Value] = k match
  case Name => Some("abc")
  case Age  => None
doesn't. (Scala 3.2.2)
Aleksander Boruch-Gruszecki. GADTs in Dotty https://www.youtube.com/watch?v=VV9lPg3fNl8
Dale Wijnand. Space Engine for Pattern Matching https://www.youtube.com/watch?v=yaxJPIsy4Js