monorepo/apps/api/src/modules/config/configure.ts

220 lines
6.9 KiB
TypeScript

import { deepMerge, isAsyncFn } from '@3rapp/utils';
import { get, has, isArray, isFunction, isNil, isObject, omit, set } from 'lodash';
import { Env } from './env';
import { ConfigStorage } from './storage';
import { ConfigStorageOption, ConfigureFactory, ConfigureRegister } from './types';
interface SetStorageOption {
/**
* 是否存储配置
*/
enabled?: boolean;
/**
* 用于指定如果该配置已经存在在config.yml中时要不要更改
*/
change?: boolean;
}
/**
* 配置类
*/
export class Configure {
/**
* 配置是否被初始化
*/
protected inited = false;
/**
* 配置构建函数对象
*/
protected factories: Record<string, ConfigureFactory<Record<string, any>>> = {};
/**
* 生成的配置
*/
protected config: Record<string, any> = {};
/**
* 环境变量操作实例
*/
protected _env: Env;
/**
* 存储配置操作实例
*/
protected storage: ConfigStorage;
/**
* 初始化配置类
* @param configs 配置构造器集合对象
* @param option 配置类选项
*/
async initilize(
configs: Record<string, ConfigureFactory<Record<string, any>>> = {},
option: ConfigStorageOption = {},
) {
if (this.inited) return this;
this._env = new Env();
await this._env.load();
const { enabled, filePath } = option;
this.storage = new ConfigStorage(enabled, filePath);
for (const key in configs) {
this.add(key, configs[key]);
}
await this.sync();
this.inited = true;
return this;
}
get env() {
return this._env;
}
/**
* 获取所有配置
*/
all() {
return this.config;
}
/**
* 判断配置是否存在
* @param key
*/
has(key: string) {
return has(this.config, key);
}
/**
* 获取配置值
* @param key
* @param defaultValue 不存在时返回的默认配置
*/
async get<T>(key: string, defaultValue?: T): Promise<T> {
if (!has(this.config, key) && defaultValue === undefined && has(this.factories, key)) {
await this.syncFactory(key);
return this.get(key, defaultValue);
}
return get(this.config, key, defaultValue) as T;
}
/**
* 设置配置项
* 如果storage是一个布尔值则只用于确定是否存储该配置,如果该配置已存在在config.yml中则不更改
* 如果storage是一个对象时,那么其中的enabled用于设置是否存储改配置,change用于指定如果该配置已经存在在config.yml中时要不要更改
* @param key 配置名
* @param value 配置值
* @param storage boolean类型时: 是否存储配置; 对象类型时: enabled 是否存储配置, change 当该配置在config.yml存在时是否改变
* @param append 如果为true,则如果已经存在的包含数组的配置,使用追加方式合并,否则直接替换
*/
set<T>(key: string, value: T, storage: SetStorageOption | boolean = false, append = false) {
const storageEnable = typeof storage === 'boolean' ? storage : !!storage.enabled;
const storageChange = typeof storage === 'boolean' ? false : !!storage.change;
if (storageEnable && this.storage.enabled) {
this.changeStorageValue(key, value, storageChange, append);
} else {
set(this.config, key, value);
}
return this;
}
/**
* 添加一个新配置集
* @param key
* @param register 配置构造器
*/
add<T extends Record<string, any>>(
key: string,
register: ConfigureRegister<T> | ConfigureFactory<T>,
) {
if (!isFunction(register) && 'register' in register) {
this.factories[key] = register as any;
} else if (isFunction(register)) {
this.factories[key] = { register };
}
return this;
}
/**
* 删除配置项
* 如果不是存储配置则为临时删除,重启用户后该配置依然存在
* @param key
*/
remove(key: string) {
if (has(this.storage.config, key) && this.storage.enabled) {
this.storage.remove(key);
this.config = deepMerge(this.config, this.storage.config, 'replace');
} else if (has(this.config, key)) {
this.config = omit(this.config, [key]);
}
return this;
}
/**
* 手动存储一个配置
* @param key 配置名
* @param change 用于指定如果该配置已经存在在config.yml中时要不要更改
* @param append 如果为true,则如果已经存在的包含数组的配置,使用追加方式合并,否则直接替换
*/
async store(key: string, change = false, append = false) {
if (!this.storage.enabled) throw new Error('Must enable storage at first!');
this.changeStorageValue(key, await this.get(key, null), change, append);
return this;
}
/**
* 同步配置
* 添加一个配置构造器后需用使用此方法同步到配置中
*/
async sync(name?: string) {
if (!isNil(name)) await this.syncFactory(name);
else {
for (const key in this.factories) {
await this.syncFactory(key);
}
}
}
/**
* 同步配置构造器
* @param key
*/
protected async syncFactory(key: string) {
if (has(this.config, key) || !has(this.factories, key)) return this;
const { register, defaultRegister, storage, hook, append } = this.factories[key];
let defaultValue = {};
let value = isAsyncFn(register) ? await register(this) : register(this);
if (!isNil(defaultRegister)) {
defaultValue = isAsyncFn(defaultRegister)
? await defaultRegister(this)
: defaultRegister(this);
value = deepMerge(defaultValue, value, 'replace');
}
if (!isNil(hook)) {
value = isAsyncFn(hook) ? await hook(this, value) : hook(this, value);
}
if (this.storage.enabled) {
value = deepMerge(value, get(this.storage.config, key, isArray(value) ? [] : {}));
}
this.set(key, value, storage && isNil(await this.get(key, null)), append);
return this;
}
protected changeStorageValue<T>(key: string, value: T, change = false, append = false) {
if (change || !has(this.storage.config, key)) {
this.storage.set(key, value);
} else if (isObject(get(this.storage.config, key))) {
this.storage.set(
key,
deepMerge(value, get(this.storage.config, key), append ? 'merge' : 'replace'),
);
}
this.config = deepMerge(this.config, this.storage.config, append ? 'merge' : 'replace');
}
}
const a = new Configure();
a.initilize();
console.log(a.env);