Big News: Socket Selected for OpenAI's Cybersecurity Grant Program.Details
Socket
Book a DemoSign in
Socket

@travetto/config

Package Overview
Dependencies
Maintainers
1
Versions
340
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@travetto/config - npm Package Compare versions

Comparing version
0.0.14
to
0.0.16
+79
src/service/loader.ts
import { bulkRead, bulkReadSync, AppEnv, bulkFindSync } from '@travetto/base';
import * as path from 'path';
import * as yaml from 'js-yaml';
import { readdirSync } from 'fs';
import { ConfigMap } from './map';
const ENV_SEP = '_';
export class ConfigLoader {
private static _initialized: boolean = false;
private static map = new ConfigMap();
static get(key: string) {
return this.map.get(key);
}
static bindTo(obj: any, key: string) {
return this.map.bindTo(obj, key);
}
/*
Order of specificity (least to most)
- Module configs -> located in the node_modules/@travetto/config folder
- Local configs -> located in the config folder
- External config file -> loaded from env
- Environment vars -> Overrides everything
*/
static initialize() {
if (this._initialized) {
return;
}
this._initialized = true;
if (!AppEnv.test) {
console.log(`Initializing: ${AppEnv.all.join(',')}`);
}
// Load all namespaces from core
let files = bulkReadSync([/^node_modules\/@travetto\/.*\/config\/.*[.]yml$/]);
// Load all configs, exclude env configs
files = files.concat(bulkReadSync([/^config\/.*[.]yml$/]));
for (const file of files) {
const ns = path.basename(file.name, '.yml');
yaml.safeLoadAll(file.data, doc => this.map.putAll({ [ns]: doc }));
}
// Handle environmental loads
if (AppEnv.all.length) {
const loaded: string[] = [];
const envFiles = bulkReadSync([/^env\/.*[.]yml$/])
.map(x => {
const tested = path.basename(x.name, '.yml');
const found = AppEnv.is(tested);
return { name: tested, found, data: x.data };
})
.filter(x => x.found);
console.debug('Found configurations for', envFiles.map(x => x.name));
for (const file of envFiles) {
yaml.safeLoadAll(file.data, doc => this.map.putAll(doc));
}
}
// Handle process.env
for (const k of Object.keys(process.env)) {
if (k.includes(ENV_SEP)) { // Require at least one level
this.map.putFlattened(k.split(ENV_SEP), process.env[k] as string);
}
}
if (!process.env.QUIET_CONFIG && !AppEnv.test) {
console.log('Configured', this.map.toJSON());
}
}
}
import { deepAssign, isPlainObject, isSimple } from '@travetto/base';
type Prim = number | string | boolean | null;
type Nested = { [key: string]: Prim | Nested | Nested[] };
function coerce(a: any, val: any): any {
if (a === 'null' && typeof val !== 'string') {
return null;
}
if (val === null || val === undefined) {
return a;
}
if (isSimple(val)) {
switch (typeof val) {
case 'string': return `${a}`;
case 'number': return `${a}`.indexOf('.') >= 0 ? parseFloat(`${a}`) : parseInt(`${a}`, 10);
case 'boolean': return (typeof a === 'string' && a === 'true') || !!a;
default:
throw new Error(`Unknown type ${typeof val}`);
}
}
if (Array.isArray(val)) {
return `${a}`.split(',').map(x => x.trim()).map(x => coerce(x, val[0]));
}
}
export class ConfigMap {
// Lowered, and flattened
private storage: Nested = {};
putAll(data: Nested) {
deepAssign(this.storage, data, 'coerce');
}
static getKeyName(key: string, data: { [key: string]: any }) {
key = key.trim();
const match = new RegExp(key, 'i');
const next = Object.keys(data).find(x => match.test(x));
return next;
}
putFlattened(parts: string[], value: Prim) {
parts = parts.slice(0);
let key = parts.pop()!;
let data = this.storage;
if (!key) {
return false;
}
while (parts.length) {
const part = parts.shift()!;
const next = ConfigMap.getKeyName(part, data);
if (!next) {
return false;
} else {
data = data[next] as Nested;
}
}
if (!data) {
return false;
}
key = ConfigMap.getKeyName(key, data) || key;
data[key] = coerce(value, data[key]);
return true;
}
bindTo(obj: any, key: string) {
const keys = key.split('.');
let sub: any = this.storage;
while (keys.length && sub[keys[0]]) {
sub = sub[keys.shift()!];
}
return deepAssign(obj, sub);
}
get(key: string) {
return this.bindTo({}, key);
}
toJSON() {
return JSON.stringify(this.storage, null, 2);
}
}
+1
-1
export const init = {
priority: 0,
action: () => require('./src/service/config-loader').ConfigLoader.initialize()
action: () => require('./src/service/loader').ConfigLoader.initialize()
}

@@ -8,5 +8,3 @@ {

"@travetto/base": "0.x.x",
"@types/flat": "0.0.28",
"@types/js-yaml": "^3.10.1",
"flat": "^4.0.0",
"js-yaml": "^3.11.0"

@@ -22,3 +20,3 @@ },

"scripts": {},
"version": "0.0.14"
"version": "0.0.16"
}
+38
-15
travetto: Config
===
This module provides common infrastructure for application based initialization. There is a common
infrastructure pattern used:
- Configuration
- A scan for all `src/app/**/config.ts`.
- Each `config.ts` file must call `registerNamespace` to define a new configuration
namespace.
- Every configuration property can be overridden via environment variables (case-insensitive).
- Object navigation is separated by underscores
- e.g. `MAIL_TRANSPORT_HOST` would override `mail.transport.host` and specifically `transport.host`
in the `mail` namespace.
- All configuration variables should be loaded before any modules use it.
- `config.ts` should not require any code from your modules to ensure the order of loading
- Bootstrap
- Supports initializing the application, and then requiring classes using a glob pattern
to handle the common initialization process.
Common functionality for reading configuration from yaml files, and allowing overriding at execution time
- Process all config information:
- `node_modules/@travetto/*/config/*.yml`
- `config/*.yml`
- `env/<env>.yml`
- `process.env` (override only)
- Depending on which environments are specified, will selectively load `env/<env>.yml` files
- Every configuration property can be overridden via environment variables (case-insensitive).
- Object navigation is separated by underscores
- e.g. `MAIL_TRANSPORT_HOST` would override `mail.transport.host` and specifically `transport.host`
in the `mail` namespace.
Provides a decorator, `@Config("namespace")` that allows for classes to automatically bind config information
on post construct. The decorator will install a `postConstruct` method if not already defined. This is a hook
that is used by other modules.
```typescript config.ts
@Config('sample')
class SampleConfig {
private host: string;
private port: number;
private creds = {
user: '',
password: ''
};
}
```
And the corresponding config file
```yaml app.yml
- sample
host: google.com
port: 80
creds:
user: bob
password: bobspw
```

@@ -1,2 +0,2 @@

import { ConfigLoader } from '../service/config-loader';
import { ConfigLoader } from '../service/loader';

@@ -3,0 +3,0 @@ export function Config(ns: string, depTarget?: new (...args: any[]) => any, name: string = '') {

@@ -1,1 +0,2 @@

export * from './config-loader';
export * from './loader';
export * from './map';
import { Config, ConfigLoader } from '../src';
import * as assert from 'assert';

@@ -17,4 +18,11 @@ class DbConfig {

if (conf.name !== 'Oscar') {
throw new Error('Should match!');
}
assert(conf.name === 'Oscar');
process.env.DB_MYSQL_NAME = 'Roger';
delete (ConfigLoader as any)['_initialized'];
ConfigLoader.initialize();
ConfigLoader.bindTo(conf, 'db.mysql');
assert(conf.name === 'Roger');
import { bulkRead, bulkReadSync, AppEnv, deepMerge, isPlainObject, bulkFindSync } from '@travetto/base';
import * as flatten from 'flat';
import * as yaml from 'js-yaml';
import { EventEmitter } from 'events';
import { readdirSync } from 'fs';
const unflatten = flatten.unflatten;
type ConfigMap = { [key: string]: string | number | boolean | null | ConfigMap };
export class ConfigLoader {
private static NULL = Symbol('NULL');
private static data: ConfigMap = {};
private static _initialized: boolean = false;
private static writeProperty(o: any, k: string, v: any) {
if (typeof v === 'string') {
if (typeof o[k] === 'boolean') {
v = v === 'true';
} else if (typeof o[k] === 'number') {
v = v.indexOf('.') >= 0 ? parseFloat(v) : parseInt(v, 10);
} else if (typeof o[k] !== 'string' && v === 'null') {
v = this.NULL;
} else if (`${k}_0` in o) { // If array
v.split(/,\s*/g).forEach((el, i) => {
this.writeProperty(o, `${k}_${i}`, el);
});
return;
}
}
o[k] = v;
}
private static merge(target: ConfigMap, source: ConfigMap, gentle = false) {
const targetFlat = flatten(target, { delimiter: '_' }) as any;
const sourceFlat = flatten(source, { delimiter: '_' }) as any;
// Flatten to lower case
const keyMap: { [key: string]: string } = {};
const lowerFlat: ConfigMap = {};
for (const k of Object.keys(targetFlat)) {
const lk = k.toLowerCase();
lowerFlat[lk] = targetFlat[k];
// handle keys, and all substrings
let end = k.length;
while (end > 0) {
const finalKey = lk.substring(0, end);
if (keyMap[finalKey]) {
break;
} else {
keyMap[finalKey] = k.substring(0, end);
end = k.lastIndexOf('_', end);
}
}
}
for (const k of Object.keys(sourceFlat)) {
const lk = k.toLowerCase();
const ns = lk.split('_', 2)[0];
if (!gentle || ns in target) {
if (!keyMap[lk]) { keyMap[lk] = k; }
this.writeProperty(lowerFlat, lk, sourceFlat[k]);
}
}
// Return original case
const out: ConfigMap = {};
for (const k of Object.keys(lowerFlat)) {
out[keyMap[k]] = lowerFlat[k];
}
deepMerge(target, unflatten(out, { delimiter: '_' }));
}
private static dropNulls(o: any) {
if (isPlainObject(o)) {
for (const k of Object.keys(o)) {
if (o[k] === this.NULL) {
delete o[k];
} else {
this.dropNulls(o[k]);
}
}
} else if (Array.isArray(o)) {
o = o.map(this.dropNulls).filter((x: any) => x !== this.NULL);
}
return o;
}
static bindTo(obj: any, key: string) {
const keys = key.split('.');
let sub: any = this.data;
while (keys.length && sub[keys[0]]) {
sub = sub[keys.shift()!];
}
deepMerge(obj, sub);
return obj;
}
static get(key: string) {
return this.bindTo({}, key);
}
/*
Order of specificity (least to most)
- Module configs -> located in the node_modules/@travetto/config folder
- Local configs -> located in the config folder
- External config file -> loaded from env
- Environment vars -> Overrides everything
*/
static initialize() {
if (this._initialized) {
return;
}
this._initialized = true;
if (!AppEnv.test) {
console.log(`Initializing: ${AppEnv.all.join(',')}`);
}
// Load all namespaces from core
let files = bulkReadSync([/^node_modules\/@travetto\/.*\/config\/.*[.]yml$/]);
// Load all configs, exclude env configs
files = files.concat(bulkReadSync([/^config\/.*[.]yml$/]));
for (const file of files) {
const ns = file.name.split('/').pop()!.split('.yml')[0];
yaml.safeLoadAll(file.data, doc => {
this.data[ns] = this.data[ns] || {};
this.merge(this.data, { [ns]: doc });
});
}
if (AppEnv.all.length) {
const loaded: string[] = [];
const envFiles = bulkReadSync([/^env\/.*[.]yml$/]).reduce((acc, x) => {
const tested = x.name.split('/').pop()!.split('.yml')[0];
const found = AppEnv.is(tested);
if (found) {
acc.push(x.data);
loaded.push(tested);
}
return acc;
}, [] as string[]);
console.debug('Found configurations for', loaded);
for (const file of envFiles) {
yaml.safeLoadAll(file, doc => {
this.merge(this.data, doc);
});
}
}
// Handle process.env
this.merge(this.data, process.env as { [key: string]: any }, true);
// Drop out nulls
this.dropNulls(this.data);
if (!process.env.QUIET_CONFIG && !AppEnv.test) {
console.log('Configured', JSON.stringify(this.data, null, 2));
}
}
}