// Рекурсивно спускаемся, пока значение — объектtype DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> // вложенный объект/массив → рекурсия : T[P]; // примитив → как есть};type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];};
Тонкости
Условие T[P] extends object отправляет в рекурсию только объекты и массивы; примитивы (string, number, …) остаются как есть.
Массивы тоже object, поэтому number[] станет readonly number[] / рекурсивно обработается — обычно это желаемое поведение.
Функции — тоже object: если не хотите в них спускаться, добавьте ветку T[P] extends Function ? T[P] : ....
Ограничение
Для Map, Set, Date наивная рекурсия попытается пройтись по их полям, что обычно не нужно. В таких случаях добавляют явные проверки: T extends Map<infer K, infer V> ? ... : ....