Tuner
Tuner - модуль для управления конфигурациями проекта. Данные конфигурации описываются в виде .ts файла с экспортируемым объектом, который содержит перечисление env переменных и полей конфига. Конфиги могут образовывать иерархию, наследуясь от родительских и перезаписываясь дочерними.
Оглавление
Простейший конфиг
Минимально конфиг может быть описан так:
// config/myConfig.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
export default Tuner.tune(
{
config: {
field1: 'value1',
field2: 100,
field3: true,
field4: ['минималистично', 'удобно', 'не правда ли?'],
},
},
);
Функция tune заботливо подскажет структуру ожидаемого объекта
Загрузка конфига и использование происходит так:
// main.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
const cfg = await Tuner.use.loadConfig();
console.log(cfg.config.field2); // 100
При запуске обязательно наличие env переменной config, ее значение - название файла конфига до .tuner.ts, в данном примере это myConfig.
config=myConfig deno run --allow-all main.ts
Конфиг с описанием env-переменных
В Tuner имеется возможность описать типы переменных окружения и поведения при их отсутствии:
- значение по умолчанию
- завершение процесса
- генерация исключения
- вычисление на лету
Например, так:
// config/myConfig.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
export default Tuner.tune(
{
env: {
// Использовать Значение по умолчанию
env1: Tuner.Env.getString.orDefault('defalut value1'),
env2: Tuner.Env.getNumber.orDefault(100),
env3: Tuner.Env.getBoolean.orDefault(true),
// Проигнорировать отсуствие переменной
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)
),
},
config: {
field1: 'value1',
field2: 100,
field3: true,
field4: ['минималистично', 'удобно', 'не правда ли?'],
},
},
);
Разумеется, можно просто указать значение-примитив, вроде env1: 100
Объединение конфигов
Tuner позволяет “собрать” конфиг, используя другие конфиги, нужно только выстроить из них цепочку:
- Текущий конфиг дополнится всеми полями родительского, при этом сохранит свои значения
- Текущий конфиг дополнится всеми полями дочернего, при этом совпадающие поля будут переписаны значениями из дочернего конфига
- Значения-фукнции, используемые для описания env-переменных также подчиняются этим правилам
flowchart LR;
subgraph W[" "]
direction BT
base["base\nРодительский конфиг\n{a: 400, b: 401, c:402}"]
rab["Рабочий конфиг\n{a: 300, b: 301}\nChild:A\nparent:base"]
A["А\nДочерний конфиг рабочего\n{b: 200, e:201}\nChild:B"]
B["B\nДочерний конфиг A\n{a: 100, d: 101}"]
end
style rab stroke:#300,stroke-width:6px
B-->|"Добавить: a=100,d=101"|A-->|"Переписать a->100, b->200\nДобавить: d=101,e=201"|rab-->|"Переписать: a->100,b->200\nДобавить: d=101, e=201"|base
W-->|Результат|F["{a: 100, b: 200, c:402, d: 101, e:201}"]
При этом, например, конфигу В необязательно указывать А в качестве родительского.
Реализация:
// config/develop.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
export default Tuner.tune({
child: Tuner.Load.local.configDir('a.tuner.ts'),
parent: Tuner.Load.local.configDir('base.tuner.ts'),
config: {
a: 300,
b: 301,
},
});
//config/base.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
export default Tuner.tune({
config: { a: 400, b: 401, c: 402 },
});
//config/a.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
export default Tuner.tune({
child: Tuner.Load.local.configDir('b.tuner.ts'),
config: {
b: 200,
e: 201,
},
});
//config/b.tuner.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
export default Tuner.tune({
config: { a: 100, d: 101 },
});
//main.ts
import Tuner from 'https://deno.land/x/tuner/mod.ts';
const cfg = await Tuner.use.loadConfig();
console.log(cfg);
//{ config: { a: 100, b: 200, c: 402, e: 201, d: 101 }, env: {} }
Tuner.Load предлагает локальный и удаленный вариант подключения конфига.
Tuner.Load.local
Функция | Вернет объект конфига из файла по … |
---|---|
absolutePath(path:string) | …указанному полному пути до него |
configDir(path:string) | …пути, относительно директории с названием “config” |
cwd(path:string) | …относительному пути в директории проекта |
Tuner.Load.remote
Фукнция | Описание | Пример (пусть файл конфигурации лежит по адресу http://some_server/b.tuner.ts) |
---|---|---|
import(path:string) | Работает, как обычный импорт | child: Tuner.Load.remote.import(”http://some_server/b.tuner.ts”) |
callbackReturnModule(cb: () ⇒ Promise<{default: ITunerConfig}>) | Принимает колбэк, который вернет промис с импортируемым модулем | child: Tuner.Load.remote.callbackReturnModule(() ⇒ import(”http://some_server/b.tuner.ts”)) |
callbackReturnString((cb: () => Promise)) | Принимает колбэк, который вернет промис с текстом модуля в виде строки (забираем код конфига из форм, блоков в Notion и тд) | child: Tuner.Load.remote.callbackReturnString(() ⇒ someFetchingFunctionStringReturned(options: {…})) |
Кроме того, Tuner.Load.remote имеет встроенные интеграции с различными сервисами через Tuner.Load.remote.providers:
- notion(key:string, blockUrl:string) - отдаем ключ авторизации(Tuner.getEnv поможет найти env-переменную в окружении или .env файле) и ссылку на блок в Notion, в котором описан модуль конфигурации
- github(key: string, owner: string, repo: string, filePath: string) - ключ, ник держателя репо, название репо и путь до файла.
Генерация схемы конфига
Для удобной работы с объектом конфигурации во время разработки рекомендуется сгенерировать тип объекта.
Tuner.use.generateSchema(obj: ObjectType, variableName: string, filePath: string) сформирует файл по пути filePath со схемой объекта obj и экспортирует тип с названием variableName, переведя первую букву в заглавный регистр.
const cfg = await Tuner.use.loadConfig();
Tuner.use.generateSchema(
cfg,
'config',
'config/configSchema.ts',
);
Файл config/configSchema.ts
import { z } from 'https://deno.land/x/zod/mod.ts';
export const configSchema = z.object({
config: z.object({
a: z.number(),
b: z.number(),
c: z.number(),
e: z.number(),
d: z.number(),
}),
env: z.object({}),
});
export type Config = z.infer<typeof configSchema>;
//├─ config
//│ ├─ a
//│ ├─ b
//│ ├─ c
//│ ├─ e
//│ └─ d
//└─ env
//