Решение за один проход с поддержкой массивов и таплов. Глубина любая.
https://www.typescriptlang.org/play/?#code/C4TwDgp...
Основное отличие от решения 
Alexandroppolus, ключи собираются последовательно и не парсятся, но результат, понятное дело, одинаковый.
type JoinKeys<T extends object> = JoinKeysDeep<T> extends infer J extends Record<
  string,
  unknown
>
  ? { [Key in J extends J ? keyof J : never]: J extends J ? J[Key] : never }
  : never;
type JoinKeysDeep<
  T extends object,
  K extends keyof T = JoinKeysDeep.GetKeys<T> & keyof T,
> = K extends K & (string | number)
  ? T[K] extends object
    ? JoinKeysDeep<T[K]> extends infer J extends Record<string, unknown>
      ? J extends J
        ? Record<`${K | '*'}.${keyof J & (string | number)}`, J[keyof J]>
        : never
      : never
    : Record<K | '*', T[K]>
  : never;
namespace JoinKeysDeep {
  export type GetKeys<T> = T extends unknown[]
    ? keyof T &
        (number extends T['length']
          ? // array
            number
          : // tuple
            `${number}`)
    : keyof T;
}