UNPKG

@kyosho-/ng-config

Version:

Configuration and options service for Angular applications.

397 lines (386 loc) 14.5 kB
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