@kyosho-/ng-config
Version:
Configuration and options service for Angular applications.
397 lines (386 loc) • 14.5 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, EventEmitter, Injectable, Inject, Optional, APP_INITIALIZER, NgModule } from '@angular/core';
import { Observable, of, forkJoin } from 'rxjs';
import { tap, map, mapTo, share, take } from 'rxjs/operators';
/**
* @license
* Copyright DagonMetric. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found under the LICENSE file in the root directory of this source tree.
*/
const CONFIG_OPTIONS = new InjectionToken('ConfigOptions');
/**
* @license
* Copyright DagonMetric. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found under the LICENSE file in the root directory of this source tree.
*/
const CONFIG_PROVIDER = new InjectionToken('ConfigProvider');
/**
* @license
* Copyright DagonMetric. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found under the LICENSE file in the root directory of this source tree.
*/
const NG_CONFIG_LOGGER = new InjectionToken('NG-CONFIG Logger');
function mapOptionValues(options, configSection) {
const keys = Object.keys(options);
for (const key of keys) {
if (!Object.prototype.hasOwnProperty.call(configSection, key)) {
continue;
}
// const popDescriptor = Object.getOwnPropertyDescriptor(options, key);
// if (!popDescriptor?.writable) {
// continue;
// }
const optionsValue = options[key];
const configValue = configSection[key];
if (configValue == null) {
options[key] = null;
continue;
}
if (optionsValue == null) {
options[key] = configValue;
continue;
}
if (optionsValue === configValue) {
continue;
}
if (typeof optionsValue === 'string') {
if (typeof configValue === 'string') {
options[key] = configValue;
}
else {
options[key] = JSON.stringify(configValue);
}
}
else if (typeof optionsValue === 'boolean') {
if (typeof configValue === 'boolean') {
options[key] = configValue;
}
else if (typeof configValue === 'string') {
options[key] = ['true', '1', 'on', 'yes'].indexOf(configValue.toLowerCase()) > -1;
}
else if (typeof configValue === 'number') {
options[key] = configValue === 1;
}
else {
options[key] = false;
}
}
else if (typeof optionsValue === 'number') {
options[key] = Number(configValue) || 0;
}
else if (Array.isArray(optionsValue)) {
if (Array.isArray(configValue)) {
if (configValue.length > 0 &&
configValue.filter((v) => typeof v == 'string').length === configValue.length) {
options[key] = [...configValue];
}
else {
options[key] = [];
}
}
else if (typeof configValue === 'string') {
options[key] = configValue
.split(';')
.map((s) => s.trim())
.filter((s) => s.length > 0);
}
}
else if (typeof optionsValue === 'object' &&
Object.prototype.toString.call(optionsValue) !== '[object Date]') {
if (!Array.isArray(configValue) && typeof configValue === 'object') {
mapOptionValues(optionsValue, configValue);
}
}
}
}
function equalDeep(a, b) {
if (a === null && b === null) {
return true;
}
if (Array.isArray(a)) {
if (!b || !Array.isArray(b)) {
return false;
}
if (a.length !== b.length) {
return false;
}
for (let i = a.length - 1; i >= 0; i--) {
if (!equalDeep(a[i], b[i])) {
return false;
}
}
return true;
}
if (Array.isArray(b)) {
return false;
}
if (a && b && typeof a == 'object' && typeof b == 'object') {
const keys = Object.keys(a);
if (keys.length !== Object.keys(b).length) {
return false;
}
for (const key of keys) {
if (!equalDeep(a[key], b[key])) {
return false;
}
}
return true;
}
return a === b;
}
/**
* @license
* Copyright DagonMetric. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found under the LICENSE file in the root directory of this source tree.
*/
/**
* The core service for loading and getting configuration value the from configuration providers.
*/
class ConfigService {
get providers() {
return this.sortedConfigProviders;
}
constructor(configProviders, injector, options, logger) {
this.injector = injector;
this.loading = false;
this.activated = false;
this.currentLoad = new Observable();
this.loadedConfig = {};
this.optionsRecord = new Map();
this.sortedConfigProviders = configProviders.reverse();
this.options = options || {};
this.valueChanges = new EventEmitter();
if (logger) {
this.logger = logger;
}
else {
this.logger = {
debug: (message, data) => {
if (data) {
// eslint-disable-next-line no-console
console.log(`${message}, data: `, data);
}
else {
// eslint-disable-next-line no-console
console.log(message);
}
},
};
}
this.currentLoad = this.initLoad();
this.subscribeCurrentLoad(false);
}
/**
* Call this method to ensure configurations are fetched and activated.
*/
ensureInitialized() {
if (this.activated) {
return of(this.activated);
}
return this.currentLoad.pipe(tap((config) => {
this.activateConfig(config, false);
}), map(() => this.activated));
}
/**
* Call this method to reload fresh configuration values from config providers.
*/
reload() {
this.currentLoad = this.initLoad();
this.subscribeCurrentLoad(true);
return this.currentLoad.pipe(mapTo(void 0));
}
/**
* Use this method to get loaded configuration value with a given string key.
* @param key The config key string.
*/
getValue(key) {
return this.getConfigValue(key, this.loadedConfig);
}
/**
* Use this method to map loaded configuration values to the instance of options class type.
* @param key The config key string.
* @param optionsClass The options class type to be mapped.
*/
mapType(key, optionsClass) {
const optionsObj = this.injector.get(optionsClass, new optionsClass());
this.mapObject(key, optionsObj);
return optionsObj;
}
/**
* Use this method to map loaded configuration values to the options object.
* @param key The config key string.
* @param optionsObj The options object to be mapped with configuration values.
*/
mapObject(key, optionsObj) {
const cachedOptions = this.optionsRecord.get(key);
if (cachedOptions != null) {
if (cachedOptions === optionsObj) {
return cachedOptions;
}
this.optionsRecord.delete(key);
}
const configValue = this.getValue(key);
if (configValue == null ||
Array.isArray(configValue) ||
typeof configValue !== 'object') {
return optionsObj;
}
mapOptionValues(optionsObj, configValue);
this.optionsRecord.set(key, optionsObj);
return optionsObj;
}
initLoad() {
if (this.currentLoadSubscription) {
this.currentLoadSubscription.unsubscribe();
this.currentLoadSubscription = null;
}
if (!this.loading) {
this.log('Cconfiguration loading started.');
this.loading = true;
}
return forkJoin(this.providers.map((configProvider) => {
const providerName = configProvider.name;
const loadObs = configProvider.load().pipe(tap((config) => {
this.log(providerName, config);
}), share());
return loadObs.pipe(take(1), share());
})).pipe(map((configs) => {
let mergedConfig = {};
configs.forEach((config) => {
mergedConfig = { ...mergedConfig, ...config };
});
return mergedConfig;
}));
}
subscribeCurrentLoad(reActivate) {
this.currentLoadSubscription = this.currentLoad.subscribe((config) => {
this.activateConfig(config, reActivate);
}, () => {
this.loading = false;
});
}
activateConfig(config, reActivate) {
this.loading = false;
if (this.activated && !reActivate) {
return;
}
if (!equalDeep(config, this.loadedConfig)) {
this.optionsRecord.clear();
this.loadedConfig = config;
this.activated = true;
this.log('Configuration loading completed.');
this.valueChanges.emit(config);
}
else {
this.activated = true;
this.log('Configuration loading completed.');
}
}
getConfigValue(key, config) {
const keyArray = key.split(/:/);
const result = keyArray.reduce((acc, current) => acc && acc[current], config);
if (result === undefined) {
return null;
}
return result;
}
log(msg, data) {
if (!this.options.debug) {
return;
}
this.logger.debug(`[ConfigService] ${msg}`, data);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.4", ngImport: i0, type: ConfigService, deps: [{ token: CONFIG_PROVIDER }, { token: i0.Injector }, { token: CONFIG_OPTIONS, optional: true }, { token: NG_CONFIG_LOGGER, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.4", ngImport: i0, type: ConfigService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.4", ngImport: i0, type: ConfigService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [CONFIG_PROVIDER]
}] }, { type: i0.Injector }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [CONFIG_OPTIONS]
}] }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [NG_CONFIG_LOGGER]
}] }]; } });
/**
* @license
* Copyright DagonMetric. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found under the LICENSE file in the root directory of this source tree.
*/
function configAppInitializerFactory(configService) {
const res = async () => configService.ensureInitialized().toPromise();
return res;
}
/**
* The `NGMODULE` for providing `ConfigService`. Call `configure` method to provide options for `ConfigService`.
*/
class ConfigModule {
/**
* Call this method in root module to provide options for `ConfigService`.
* @param loadOnStartUp If `true` configuration values are loaded at app starts. Default is `true`.
* @param options Option object for `ConfigService`.
*/
static configure(loadOnStartUp = true, options = {}) {
return {
ngModule: ConfigModule,
providers: [
{
provide: CONFIG_OPTIONS,
useValue: options,
},
loadOnStartUp
? {
provide: APP_INITIALIZER,
useFactory: configAppInitializerFactory,
deps: [ConfigService],
multi: true,
}
: [],
],
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.4", ngImport: i0, type: ConfigModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.4", ngImport: i0, type: ConfigModule }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.4", ngImport: i0, type: ConfigModule, providers: [ConfigService] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.4", ngImport: i0, type: ConfigModule, decorators: [{
type: NgModule,
args: [{
providers: [ConfigService],
}]
}] });
/**
* @license
* Copyright DagonMetric. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found under the LICENSE file in the root directory of this source tree.
*/
/*
* Public API Surface of ng-config
*/
/**
* Generated bundle index. Do not edit.
*/
export { CONFIG_OPTIONS, CONFIG_PROVIDER, ConfigModule, ConfigService, NG_CONFIG_LOGGER, configAppInitializerFactory };
//# sourceMappingURL=kyosho--ng-config.mjs.map