add config module
This commit is contained in:
parent
7b6a5ca24e
commit
b2189a8c5f
53
src/modules/config/ConfigStorage.ts
Normal file
53
src/modules/config/ConfigStorage.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { resolve } from 'path';
|
||||
|
||||
import { ensureFileSync, readFileSync, writeFileSync } from 'fs-extra';
|
||||
import { has, isNil, omit, set } from 'lodash';
|
||||
import { parse } from 'yaml';
|
||||
|
||||
export class ConfigStorage {
|
||||
protected _enabled = false;
|
||||
|
||||
protected _path = resolve(__dirname, '../../..', 'config.yaml');
|
||||
|
||||
protected _config: RecordAny = {};
|
||||
|
||||
get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
get path() {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
constructor(enabled?: boolean, filePath?: string) {
|
||||
if (!isNil(enabled)) {
|
||||
this._enabled = enabled;
|
||||
}
|
||||
if (this._enabled) {
|
||||
if (!isNil(filePath)) {
|
||||
this._path = filePath;
|
||||
}
|
||||
ensureFileSync(this._path);
|
||||
const config = parse(readFileSync(this._path, 'utf-8'));
|
||||
this._config = isNil(config) ? {} : config;
|
||||
}
|
||||
}
|
||||
|
||||
set<T>(key: string, value: T) {
|
||||
ensureFileSync(this.path);
|
||||
set(this._config, key, value);
|
||||
writeFileSync(this.path, JSON.stringify(this._config, null, 4));
|
||||
}
|
||||
|
||||
remove(key: string) {
|
||||
this._config = omit(this._config, [key]);
|
||||
if (has(this._config, key)) {
|
||||
omit(this._config, [key]);
|
||||
}
|
||||
writeFileSync(this.path, JSON.stringify(this._config, null, 4));
|
||||
}
|
||||
}
|
20
src/modules/config/config.module.ts
Normal file
20
src/modules/config/config.module.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { DynamicModule, Module } from '@nestjs/common';
|
||||
|
||||
import { Configure } from './configure';
|
||||
|
||||
@Module({})
|
||||
export class ConfigModule {
|
||||
static forRoot(configure: Configure): DynamicModule {
|
||||
return {
|
||||
global: true,
|
||||
module: ConfigModule,
|
||||
providers: [
|
||||
{
|
||||
provide: Configure,
|
||||
useValue: configure,
|
||||
},
|
||||
],
|
||||
exports: [Configure],
|
||||
};
|
||||
}
|
||||
}
|
144
src/modules/config/configure.ts
Normal file
144
src/modules/config/configure.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import { get, has, isArray, isFunction, isNil, isObject, omit, set } from 'lodash';
|
||||
|
||||
import { deepMerge, isAsyncFunction } from '../core/helpers';
|
||||
|
||||
import { ConfigStorage } from './ConfigStorage';
|
||||
import { Env } from './env';
|
||||
import { ConfigStorageOption, ConfigureFactory, ConfigureRegister } from './types';
|
||||
|
||||
interface SetStorageOption {
|
||||
enabled?: boolean;
|
||||
change?: boolean;
|
||||
}
|
||||
|
||||
export class Configure {
|
||||
protected inited = false;
|
||||
|
||||
protected factories: Record<string, ConfigureFactory<RecordAny>> = {};
|
||||
|
||||
protected config: RecordAny = {};
|
||||
|
||||
protected _env: Env;
|
||||
|
||||
protected storage: ConfigStorage;
|
||||
|
||||
get env() {
|
||||
return this._env;
|
||||
}
|
||||
|
||||
all() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
has(key: string) {
|
||||
return has(this.config, key);
|
||||
}
|
||||
|
||||
async initialize(configs: RecordAny = {}, options: ConfigStorageOption = {}) {
|
||||
if (this.inited) {
|
||||
return this;
|
||||
}
|
||||
this._env = new Env();
|
||||
await this._env.load();
|
||||
const { enable, filePath } = options;
|
||||
this.storage = new ConfigStorage(enable, filePath);
|
||||
for (const key of Object.keys(configs)) {
|
||||
this.add(key, configs[key]);
|
||||
}
|
||||
await this.sync();
|
||||
this.inited = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async sync(name?: string) {
|
||||
if (isNil(name)) {
|
||||
for (const key in this.factories) {
|
||||
await this.syncFactory(key);
|
||||
}
|
||||
} else {
|
||||
await this.syncFactory(name);
|
||||
}
|
||||
}
|
||||
|
||||
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 = isAsyncFunction(register) ? await register(this) : register(this);
|
||||
if (!isNil(defaultRegister)) {
|
||||
defaultValue = isAsyncFunction(defaultRegister)
|
||||
? await defaultRegister(this)
|
||||
: defaultRegister(this);
|
||||
value = deepMerge(defaultValue, value, 'replace');
|
||||
}
|
||||
|
||||
if (!isNil(hook)) {
|
||||
value = isAsyncFunction(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;
|
||||
}
|
||||
|
||||
add<T extends RecordAny>(key: string, register: ConfigureFactory<T> | ConfigureRegister<T>) {
|
||||
if (!isFunction(register) && 'register' in register) {
|
||||
this.factories[key] = register as any;
|
||||
} else if (isFunction(register)) {
|
||||
this.factories[key] = { register };
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
remove(key: string) {
|
||||
if (this.storage.enabled && has(this.storage.config, key)) {
|
||||
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;
|
||||
}
|
||||
|
||||
async store(key: string, change = false, append = false) {
|
||||
if (!this.storage.enabled) {
|
||||
throw new Error('Must enable storage first');
|
||||
}
|
||||
this.changeStorageValue(key, await this.get(key, null), change, 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');
|
||||
}
|
||||
}
|
24
src/modules/config/types.ts
Normal file
24
src/modules/config/types.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Configure } from './configure';
|
||||
|
||||
export interface ConfigStorageOption {
|
||||
enable?: boolean;
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
export type ConfigureRegister<T extends RecordAny> = (configure: Configure) => T | Promise<T>;
|
||||
|
||||
export interface ConfigureFactory<T extends RecordAny, P extends RecordAny = T> {
|
||||
register: ConfigureRegister<RePartial<T>>;
|
||||
|
||||
defaultRegister?: ConfigureRegister<T>;
|
||||
|
||||
storage?: boolean;
|
||||
|
||||
hook?: (configure: Configure, value: T) => P | Promise<P>;
|
||||
|
||||
append?: boolean;
|
||||
}
|
||||
|
||||
export type ConnectionOption<T extends RecordAny> = { name?: string } & T;
|
||||
|
||||
export type ConnectionRst<T extends RecordAny> = Array<{ name?: string } & T>;
|
@ -32,3 +32,10 @@ export const deepMerge = <T, P>(
|
||||
}
|
||||
return deepmerge(x, y, options) as P extends T ? T : T & P;
|
||||
};
|
||||
|
||||
export function isAsyncFunction<T, P extends Array<any>>(
|
||||
callback: (...args: P) => T | Promise<T>,
|
||||
): callback is (...args: P) => Promise<T> {
|
||||
const AsyncFunction = (async () => {}).constructor;
|
||||
return callback instanceof AsyncFunction === true;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user