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;
|
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