Как правильно составить типы для валидации объекта?

Пытаюсь сделать функцию, которая бы создавала конфиг для сайта. То есть первым аргументом идет общий конфиг для всех env'ов, а дальше объект, в котором ключи - это названия env'ов, а значения - конфиги, специфичные для каждого env'а.

То есть логика такая: первым аргументом может быть объект с любыми ключами и значениями. А вот последующие конфиги могут либо перезаписывать дефолтные ключи (таким же типом данных) либо добавлять новые ключи. Но при этом, если в каком-то из конфигов есть ключ, которого нет в дефолтном конфиге, он обязательно должен быть прописан во всех конфигах.

Надеюсь с примером будет понятно. У меня что-то получилось, но работает не до конца верно (такой вариант пропускает пустые конфиги). Да и вангую что есть более лаконичное решение, мое попахивает говнокодом. Не понимаю почему мне так сложно TS дается, понимаю как все работает по отдельности, но когда нужно какой-то сложный generic написать, начинаю жутко тупить.

export const createConfig = <
    Default extends Record<string, any>,
    Configs extends Record<string, Record<string, any>>,
    X = Configs extends Record<string, infer X> ? Omit<X, keyof Default> : never
>(
    common: Default,
    configs: {
        [env in keyof Configs]: {
            [key in keyof Configs[env]]: key extends keyof Default ? Default[key] : (
                key extends keyof X ? (
                    X[key] extends Configs[env][key] ? Configs[env][key] : never
                ) : never
            )
        }
    }
) => {}

Должно ругаться на пустой конфиг для staging:

const config = createConfig(
    {
        test: 12345,
        hello: true
    },
    {
        staging: {
            // hostname: ""
        },
        production: {
            hostname: "hello"
        }
    },
)
  • Вопрос задан
  • 118 просмотров
Пригласить эксперта
Ответы на вопрос 1
@slide13
frontend/web-developer
Единственное, что сходу приходит в голову - это взять какой-то объект конфига за основу и сравнивать остальные с ним, а так как у нас нет явного указания какие свойства у конфига есть, то непонятно, что брать за основу. Возможно, есть какой-то более адекватный вариант, но рабочий придумал только такой:

type CommonConfig = Record<string, any>;

const createConfig = <
  Common extends CommonConfig,
  Configs extends Record<string, ProductionConfig> & {
    production: ProductionConfig;
  },
  ProductionConfig = Configs["production"] & Partial<Common>,
>(
  common: Common,
  configs: {
    [env in keyof Configs]: Configs[env];
  }
) => ({});


Т.е. мы требуем, чтобы в конфиге был конфиг "production" и все остальные конфиги сравниваем с ним, ну и плюс он может содержать опционально свойства дефолтного конфига.

Такой вариант рабочий, но вообще, для валидации конфигов я бы взял zod
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы