Tuner

deno.land/x/tuner JSR Score

Tuner - модуль для управления конфигурациями проекта. Данные конфигурации описываются в виде .ts файла с экспортируемым объектом, который содержит перечисление env переменных и полей конфига. Конфиги могут образовывать иерархию, наследуясь от родительских и перезаписываясь дочерними.


Оглавление

Простейший конфиг

Минимально конфиг может быть описан так:

// ./config/myConfig.tuner.ts
import Tuner from 'jsr:@artpani/tuner';

export default Tuner.tune(
  {
    data: {
      field1: 'value1',
      field2: 100,
      field3: true,
      field4: ['минималистично', 'удобно', 'не правда ли?'],
    },
  },
);

export default myCfg;
export type MyCFGType = typeof myCfg;

Функция tune заботливо подскажет структуру ожидаемого объекта

Загрузка конфига и использование происходит так:

// ./main.ts
import Tuner from 'jsr:@artpani/tuner';
import { MyCFGType } from '../config/myConfig.tuner.ts';
const cfg = await Tuner.use.loadConfig<MyCFGType>({
  configDirPath: 'config',
});
console.log(cfg.data.field2); // 100

При запуске обязательно наличие env переменной CONFIG, ее значение - название файла конфига до .tuner.ts, в данном примере это myConfig.

CONFIG=myConfig deno run --allow-all main.ts

Конфиг с описанием env-переменных

В Tuner имеется возможность описать типы переменных окружения и поведения при их отсутствии:

  • значение по умолчанию
  • завершение процесса
  • генерация исключения
  • вычисление на лету

Например, так:

// ./config/myConfig.tuner.ts
import Tuner from 'jsr:@artpani/tuner';
export default Tuner.tune(
  {
    env: {
      // Использовать Значение по умолчанию
      env1: Tuner.Env.getString.orDefault('defalut value1'),
      env2: Tuner.Env.getNumber.orDefault(100),
      env3: Tuner.Env.getBoolean.orDefault(true),
      // Проигнорировать отсуствие переменной(будет иметь тип значения + undefined)
      env4: Tuner.Env.getString.orNothing(),
      env5: Tuner.Env.getNumber.orNothing(),
      env6: Tuner.Env.getBoolean.orNothing(),
      // Завершенить процесс
      env7: Tuner.Env.getString.orExit(
        'сообщение об ошибке, необязательно',
      ),
      env8: Tuner.Env.getNumber.orExit(
        'выведет в консоль перед выходом',
      ),
      env9: Tuner.Env.getBoolean.orExit(),
      // Сгенерировать исключение
      env10: Tuner.Env.getString.orThrow(new Error('ошибка')),
      env11: Tuner.Env.getNumber.orThrow(new Error()),
      env12: Tuner.Env.getBoolean.orThrow(new Error()),
      // Вычисленить данных по переданному колбэку
      //(может быть асинхронным, если данные нужно получить с диска или удаленно, например)
      env13: Tuner.Env.getString.orCompute(() => 'computed value1'),
      env14: Tuner.Env.getNumber.orAsyncCompute(() =>
        new Promise(() => 100)
      ),
    },
    data: {
      field1: 'value1',
      field2: 100,
      field3: true,
      field4: ['минималистично', 'удобно', 'не правда ли?'],
    },
  },
);

Разумеется, можно просто указать значение-примитив, вроде env1: 100

Объединение конфигов

Tuner позволяет “собрать” конфиг, используя другие конфиги, нужно только выстроить из них цепочку:

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

Пример наследования

При этом, например, конфигу В необязательно указывать А в качестве родительского.

Реализация:

// config/develop.tuner.ts
import Tuner from 'jsr:@artpani/tuner';
import { ACfgType } from './a.tuner.ts';

const developCfg = Tuner.tune({
  parent: Tuner.Load.local.configDir<ACfgType>('a.tuner.ts'),
  data: {
    a: 300,
    b: 301,
  },
});

export default developCfg;
export type DevelopCFGType = typeof developCfg;

// config/base.tuner.ts
import Tuner from 'jsr:@artpani/tuner';

const baseCfg = Tuner.tune({
  data: {
    a: 400,
    b: 401,
    c: 402,
  },
});

export default baseCfg;
export type BaseCFGType = typeof baseCfg;

// config/a.tuner.ts
import Tuner from 'jsr:@artpani/tuner';
import { BaseCFGType } from './base.tuner.ts';

const aCfg = Tuner.tune({
  parent: Tuner.Load.local.configDir<BaseCFGType>('base.tuner.ts'),
  child: Tuner.Load.local.configDir('b.tuner.ts'),
  data: {
    b: 200,
    e: 201,
  },
});

export default aCfg;
export type ACfgType = typeof aCfg;

// config/b.tuner.ts
import Tuner from 'jsr:@artpani/tuner';

const bCfg = Tuner.tune({
  data: {
    a: 100,
    d: 101,
  },
});

export default bCfg;
export type BCfgType = typeof bCfg;

// main.ts
import { DevelopCFGType } from './config/develop.tuner.ts';
import Tuner from 'jsr:@artpani/tuner';

const config = await Tuner.use.loadConfig<DevelopCFGType>({
  configDirPath: './config',
});

console.log(config.data);
// { a: 300, b: 301, c: 402, e: 201, d: 101 }

Опции при загрузке

При вызове loadConfig, передайте объект с опциями, чтобы настроить процесс загрузки конфигураций:

  • configDirPath: Путь к директории, содержащей конфигурационные файлы. По умолчанию это директория (./config). Используйте эту опцию, если конфигурационные файлы находятся в другом месте.