@travetto/config 3.4.0-rc.5 → 3.4.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -18
- package/package.json +4 -4
- package/src/parser/parser.ts +6 -1
- package/src/service.ts +16 -28
- package/src/source/env.ts +5 -8
- package/src/source/file.ts +38 -26
- package/src/source/memory.ts +5 -9
- package/src/source/override.ts +3 -6
- package/src/source/types.ts +6 -3
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Config loading follows a defined resolution path, below is the order in increasi
|
|
|
23
23
|
1. `resources/application.<ext>` - Priority `100` - Load the default `application.<ext>` if available.
|
|
24
24
|
1. `resources/{env}.<ext>` - Priority `200` - Load environment specific profile configurations as defined by the values of `process.env.TRV_ENV`.
|
|
25
25
|
1. `resources/*.<ext>` - Priority `300` - Load profile specific configurations as defined by the values in `process.env.TRV_PROFILES`
|
|
26
|
-
1. [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L31) [ConfigSource](https://github.com/travetto/travetto/tree/main/module/config/src/source/types.ts#
|
|
26
|
+
1. [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L31) [ConfigSource](https://github.com/travetto/travetto/tree/main/module/config/src/source/types.ts#L11) - Priority `???` - These are custom config sources provided by the module, and are able to define their own priorities
|
|
27
27
|
1. [OverrideConfigSource](https://github.com/travetto/travetto/tree/main/module/config/src/source/override.ts#L11) - Priority `999` - This is for [EnvVar](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L34) overrides, and is at the top priority for all built-in config sources.
|
|
28
28
|
By default all configuration data is inert, and will only be applied when constructing an instance of a configuration class.
|
|
29
29
|
|
|
@@ -96,24 +96,20 @@ The framework provides two simple base classes that assist with existing pattern
|
|
|
96
96
|
**Code: Memory Provider**
|
|
97
97
|
```typescript
|
|
98
98
|
import { ConfigData } from '../parser/types';
|
|
99
|
-
import { ConfigSource } from './types';
|
|
99
|
+
import { ConfigSource, ConfigSpec } from './types';
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* Meant to be instantiated and provided as a unique config source
|
|
103
103
|
*/
|
|
104
104
|
export class MemoryConfigSource implements ConfigSource {
|
|
105
|
-
|
|
106
|
-
data: ConfigData;
|
|
107
|
-
source: string;
|
|
105
|
+
#spec: ConfigSpec;
|
|
108
106
|
|
|
109
107
|
constructor(key: string, data: ConfigData, priority: number = 500) {
|
|
110
|
-
this
|
|
111
|
-
this.priority = priority;
|
|
112
|
-
this.source = `memory://${key}`;
|
|
108
|
+
this.#spec = { data, priority, source: `memory://${key}` };
|
|
113
109
|
}
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
return this
|
|
111
|
+
get(): ConfigSpec {
|
|
112
|
+
return this.#spec;
|
|
117
113
|
}
|
|
118
114
|
}
|
|
119
115
|
```
|
|
@@ -122,27 +118,24 @@ export class MemoryConfigSource implements ConfigSource {
|
|
|
122
118
|
```typescript
|
|
123
119
|
import { Env } from '@travetto/base';
|
|
124
120
|
|
|
125
|
-
import { ConfigSource } from './types';
|
|
126
|
-
import { ConfigData } from '../parser/types';
|
|
121
|
+
import { ConfigSource, ConfigSpec } from './types';
|
|
127
122
|
|
|
128
123
|
/**
|
|
129
124
|
* Represents the environment mapped data as a JSON blob
|
|
130
125
|
*/
|
|
131
126
|
export class EnvConfigSource implements ConfigSource {
|
|
132
|
-
priority: number;
|
|
133
|
-
source: string;
|
|
134
127
|
#envKey: string;
|
|
128
|
+
#priority: number;
|
|
135
129
|
|
|
136
130
|
constructor(key: string, priority: number) {
|
|
137
131
|
this.#envKey = key;
|
|
138
|
-
this
|
|
139
|
-
this.source = `env://${this.#envKey}`;
|
|
132
|
+
this.#priority = priority;
|
|
140
133
|
}
|
|
141
134
|
|
|
142
|
-
|
|
135
|
+
get(): ConfigSpec | undefined {
|
|
143
136
|
try {
|
|
144
137
|
const data = JSON.parse(Env.get(this.#envKey, '{}'));
|
|
145
|
-
return data;
|
|
138
|
+
return { data, priority: this.#priority, source: `env://${this.#envKey}` };
|
|
146
139
|
} catch (e) {
|
|
147
140
|
console.error(`env.${this.#envKey} is an invalid format`, { text: Env.get(this.#envKey) });
|
|
148
141
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/config",
|
|
3
|
-
"version": "3.4.0-rc.
|
|
3
|
+
"version": "3.4.0-rc.6",
|
|
4
4
|
"description": "Configuration support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"yaml",
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"directory": "module/config"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/di": "^3.4.0-rc.
|
|
30
|
-
"@travetto/schema": "^3.4.0-rc.
|
|
31
|
-
"@travetto/yaml": "^3.4.0-rc.
|
|
29
|
+
"@travetto/di": "^3.4.0-rc.6",
|
|
30
|
+
"@travetto/schema": "^3.4.0-rc.6",
|
|
31
|
+
"@travetto/yaml": "^3.4.0-rc.6"
|
|
32
32
|
},
|
|
33
33
|
"travetto": {
|
|
34
34
|
"displayName": "Configuration"
|
package/src/parser/parser.ts
CHANGED
|
@@ -19,10 +19,12 @@ export class ParserManager {
|
|
|
19
19
|
|
|
20
20
|
// Register parsers
|
|
21
21
|
this.#parsers = Object.fromEntries(parsers.flatMap(p => p.ext.map(e => [e, p])));
|
|
22
|
-
|
|
23
22
|
this.#extMatch = parsers.length ? new RegExp(`(${Object.keys(this.#parsers).join('|').replaceAll('.', '[.]')})`) : /^$/;
|
|
24
23
|
}
|
|
25
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Attempt ot parse a file, based on file's extension
|
|
27
|
+
*/
|
|
26
28
|
async parse(file: string): Promise<ConfigData> {
|
|
27
29
|
const ext = path.extname(file);
|
|
28
30
|
if (!this.#parsers[ext]) {
|
|
@@ -31,6 +33,9 @@ export class ParserManager {
|
|
|
31
33
|
return fs.readFile(file, 'utf8').then(content => this.#parsers[ext].parse(content));
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Determine if file matches
|
|
38
|
+
*/
|
|
34
39
|
matches(file: string): boolean {
|
|
35
40
|
return this.#extMatch.test(file);
|
|
36
41
|
}
|
package/src/service.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import util from 'util';
|
|
2
2
|
|
|
3
|
-
import { AppError, Class, ClassInstance, GlobalEnv, DataUtil
|
|
3
|
+
import { AppError, Class, ClassInstance, GlobalEnv, DataUtil } from '@travetto/base';
|
|
4
4
|
import { DependencyRegistry, Injectable } from '@travetto/di';
|
|
5
5
|
import { RootIndex } from '@travetto/manifest';
|
|
6
6
|
import { BindUtil, SchemaRegistry, SchemaValidator, ValidationResultError } from '@travetto/schema';
|
|
@@ -8,11 +8,11 @@ import { BindUtil, SchemaRegistry, SchemaValidator, ValidationResultError } from
|
|
|
8
8
|
import { ConfigSourceTarget, ConfigTarget } from './internal/types';
|
|
9
9
|
import { ParserManager } from './parser/parser';
|
|
10
10
|
import { ConfigData } from './parser/types';
|
|
11
|
-
import { ConfigSource } from './source/types';
|
|
11
|
+
import { ConfigSource, ConfigSpec } from './source/types';
|
|
12
12
|
import { FileConfigSource } from './source/file';
|
|
13
13
|
import { OverrideConfigSource } from './source/override';
|
|
14
14
|
|
|
15
|
-
type
|
|
15
|
+
type ConfigSpecSimple = Omit<ConfigSpec, 'data'>;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Manager for application configuration
|
|
@@ -21,22 +21,9 @@ type ConfigSpec = { source: string, priority: number, detail?: string };
|
|
|
21
21
|
export class ConfigurationService {
|
|
22
22
|
|
|
23
23
|
#storage: Record<string, unknown> = {}; // Lowered, and flattened
|
|
24
|
-
#specs:
|
|
24
|
+
#specs: ConfigSpecSimple[] = [];
|
|
25
25
|
#secrets: (RegExp | string)[] = [/secure(-|_|[a-z])|password|private|secret|salt|(api(-|_)?key)/i];
|
|
26
26
|
|
|
27
|
-
async #toSpecPairs(cfg: ConfigSource): Promise<[ConfigSpec, ConfigData][]> {
|
|
28
|
-
const data = await cfg.getData();
|
|
29
|
-
if (!data) {
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
const arr = Array.isArray(data) ? data : [data];
|
|
33
|
-
return arr.map((d, i) => [{
|
|
34
|
-
priority: cfg.priority + i,
|
|
35
|
-
source: cfg.source,
|
|
36
|
-
...(d.__ID__ ? { detail: d.__ID__?.toString() } : {})
|
|
37
|
-
}, d]);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
27
|
/**
|
|
41
28
|
* Get a sub tree of the config, or everything if namespace is not passed
|
|
42
29
|
* @param ns The namespace of the config to search for, can be dotted for accessing sub namespaces
|
|
@@ -69,22 +56,23 @@ export class ConfigurationService {
|
|
|
69
56
|
|
|
70
57
|
const parser = await DependencyRegistry.getInstance(ParserManager);
|
|
71
58
|
|
|
72
|
-
const
|
|
73
|
-
new FileConfigSource(parser
|
|
74
|
-
new FileConfigSource(parser, GlobalEnv.envName, 200),
|
|
75
|
-
...(Env.getList('TRV_PROFILES') ?? []).map((p, i) => new FileConfigSource(parser, p, 300 + i * 10)),
|
|
59
|
+
const possible = await Promise.all([
|
|
60
|
+
new FileConfigSource(parser),
|
|
76
61
|
...configs,
|
|
77
62
|
new OverrideConfigSource()
|
|
78
|
-
].map(src =>
|
|
63
|
+
].map(src => src.get()));
|
|
79
64
|
|
|
80
|
-
const specs =
|
|
65
|
+
const specs = possible
|
|
66
|
+
.flat()
|
|
67
|
+
.filter((x): x is Exclude<typeof x, undefined> => !!x)
|
|
68
|
+
.sort((a, b) => a.priority - b.priority);
|
|
81
69
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
for (const [, element] of specs) {
|
|
85
|
-
DataUtil.deepAssign(this.#storage, BindUtil.expandPaths(element), 'coerce');
|
|
70
|
+
for (const spec of specs) {
|
|
71
|
+
DataUtil.deepAssign(this.#storage, BindUtil.expandPaths(spec.data), 'coerce');
|
|
86
72
|
}
|
|
87
73
|
|
|
74
|
+
this.#specs = specs.map(({ data, ...v }) => v);
|
|
75
|
+
|
|
88
76
|
// Initialize Secrets
|
|
89
77
|
const userSpecified = (this.#get('config')?.secrets ?? []);
|
|
90
78
|
for (const el of Array.isArray(userSpecified) ? userSpecified : [userSpecified]) {
|
|
@@ -102,7 +90,7 @@ export class ConfigurationService {
|
|
|
102
90
|
* Export all active configuration, useful for displaying active state
|
|
103
91
|
* - Will not show fields marked as secret
|
|
104
92
|
*/
|
|
105
|
-
async exportActive(): Promise<{ sources:
|
|
93
|
+
async exportActive(): Promise<{ sources: ConfigSpecSimple[], active: ConfigData }> {
|
|
106
94
|
const configTargets = await DependencyRegistry.getCandidateTypes(ConfigTarget);
|
|
107
95
|
const configs = await Promise.all(
|
|
108
96
|
configTargets
|
package/src/source/env.ts
CHANGED
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
import { Env } from '@travetto/base';
|
|
2
2
|
|
|
3
|
-
import { ConfigSource } from './types';
|
|
4
|
-
import { ConfigData } from '../parser/types';
|
|
3
|
+
import { ConfigSource, ConfigSpec } from './types';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Represents the environment mapped data as a JSON blob
|
|
8
7
|
*/
|
|
9
8
|
export class EnvConfigSource implements ConfigSource {
|
|
10
|
-
priority: number;
|
|
11
|
-
source: string;
|
|
12
9
|
#envKey: string;
|
|
10
|
+
#priority: number;
|
|
13
11
|
|
|
14
12
|
constructor(key: string, priority: number) {
|
|
15
13
|
this.#envKey = key;
|
|
16
|
-
this
|
|
17
|
-
this.source = `env://${this.#envKey}`;
|
|
14
|
+
this.#priority = priority;
|
|
18
15
|
}
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
get(): ConfigSpec | undefined {
|
|
21
18
|
try {
|
|
22
19
|
const data = JSON.parse(Env.get(this.#envKey, '{}'));
|
|
23
|
-
return data;
|
|
20
|
+
return { data, priority: this.#priority, source: `env://${this.#envKey}` };
|
|
24
21
|
} catch (e) {
|
|
25
22
|
console.error(`env.${this.#envKey} is an invalid format`, { text: Env.get(this.#envKey) });
|
|
26
23
|
}
|
package/src/source/file.ts
CHANGED
|
@@ -1,42 +1,54 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
|
|
3
|
+
import { Env, FileResourceProvider, GlobalEnv } from '@travetto/base';
|
|
2
4
|
import { RootIndex, path } from '@travetto/manifest';
|
|
3
5
|
|
|
4
|
-
import { ConfigSource } from './types';
|
|
6
|
+
import { ConfigSource, ConfigSpec } from './types';
|
|
5
7
|
import { ParserManager } from '../parser/parser';
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
type Profile = [string, number] | readonly [string, number];
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* File-base config source, builds on common file resource provider
|
|
10
13
|
*/
|
|
11
|
-
export class FileConfigSource
|
|
14
|
+
export class FileConfigSource implements ConfigSource {
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
parser: ParserManager;
|
|
16
|
+
#profiles: Profile[];
|
|
17
|
+
#searchPaths: string[];
|
|
18
|
+
#parser: ParserManager;
|
|
17
19
|
|
|
18
|
-
constructor(parser: ParserManager,
|
|
19
|
-
|
|
20
|
-
this
|
|
21
|
-
this
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
constructor(parser: ParserManager, paths?: string[], profiles?: Profile[]) {
|
|
21
|
+
this.#parser = parser;
|
|
22
|
+
this.#searchPaths = FileResourceProvider.resolveSearchPaths({ includeCommon: true, paths }).reverse();
|
|
23
|
+
this.#profiles = profiles ?? [
|
|
24
|
+
['application', 100],
|
|
25
|
+
[GlobalEnv.envName, 200],
|
|
26
|
+
...(Env.getList('TRV_PROFILES') ?? [])
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
28
|
+
.map((p, i) => [p, 300 + i * 10] as [string, number])
|
|
29
|
+
];
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
async
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
async get(): Promise<ConfigSpec[]> {
|
|
33
|
+
const cache: Record<string, Promise<string[]>> = {};
|
|
34
|
+
const configs: Promise<ConfigSpec>[] = [];
|
|
35
|
+
for (const [profile, priority] of this.#profiles) {
|
|
36
|
+
let i = 0;
|
|
37
|
+
for (const folder of this.#searchPaths) {
|
|
38
|
+
const files = await (cache[folder] ??= fs.readdir(folder).catch(() => []));
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
if (this.#parser.matches(file) && path.basename(file, path.extname(file)) === profile) {
|
|
41
|
+
const full = path.resolve(folder, file);
|
|
42
|
+
configs.push(this.#parser.parse(full).then(data => ({
|
|
43
|
+
data,
|
|
44
|
+
priority: priority + i++,
|
|
45
|
+
source: `file://${profile}`,
|
|
46
|
+
detail: full.replace(`${RootIndex.manifest.workspacePath}/`, '')
|
|
47
|
+
})));
|
|
48
|
+
}
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
51
|
}
|
|
40
|
-
return
|
|
52
|
+
return Promise.all(configs);
|
|
41
53
|
}
|
|
42
54
|
}
|
package/src/source/memory.ts
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import { ConfigData } from '../parser/types';
|
|
2
|
-
import { ConfigSource } from './types';
|
|
2
|
+
import { ConfigSource, ConfigSpec } from './types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Meant to be instantiated and provided as a unique config source
|
|
6
6
|
*/
|
|
7
7
|
export class MemoryConfigSource implements ConfigSource {
|
|
8
|
-
|
|
9
|
-
data: ConfigData;
|
|
10
|
-
source: string;
|
|
8
|
+
#spec: ConfigSpec;
|
|
11
9
|
|
|
12
10
|
constructor(key: string, data: ConfigData, priority: number = 500) {
|
|
13
|
-
this
|
|
14
|
-
this.priority = priority;
|
|
15
|
-
this.source = `memory://${key}`;
|
|
11
|
+
this.#spec = { data, priority, source: `memory://${key}` };
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
return this
|
|
14
|
+
get(): ConfigSpec {
|
|
15
|
+
return this.#spec;
|
|
20
16
|
}
|
|
21
17
|
}
|
package/src/source/override.ts
CHANGED
|
@@ -2,16 +2,13 @@ import { SchemaRegistry } from '@travetto/schema';
|
|
|
2
2
|
|
|
3
3
|
import { ConfigOverrides, CONFIG_OVERRIDES } from '../internal/types';
|
|
4
4
|
import { ConfigData } from '../parser/types';
|
|
5
|
-
import { ConfigSource } from './types';
|
|
5
|
+
import { ConfigSource, ConfigSpec } from './types';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Overridable config source, provides ability to override field level values, currently used by
|
|
9
9
|
* - @EnvVar as a means to allow environment specific overrides
|
|
10
10
|
*/
|
|
11
11
|
export class OverrideConfigSource implements ConfigSource {
|
|
12
|
-
priority = 999;
|
|
13
|
-
source = 'memory://override';
|
|
14
|
-
|
|
15
12
|
#build(): ConfigData {
|
|
16
13
|
const out: ConfigData = {};
|
|
17
14
|
for (const cls of SchemaRegistry.getClasses()) {
|
|
@@ -26,7 +23,7 @@ export class OverrideConfigSource implements ConfigSource {
|
|
|
26
23
|
return out;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
return this.#build();
|
|
26
|
+
get(): ConfigSpec {
|
|
27
|
+
return { data: this.#build(), priority: 999, source: 'memory://override' };
|
|
31
28
|
}
|
|
32
29
|
}
|
package/src/source/types.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { ConfigData } from '../parser/types';
|
|
2
2
|
|
|
3
|
+
type OrProm<T> = T | Promise<T>;
|
|
4
|
+
type OneOf<T> = T[] | T | undefined;
|
|
5
|
+
|
|
6
|
+
export type ConfigSpec = { data: ConfigData, priority: number, source: string, detail?: string };
|
|
7
|
+
|
|
3
8
|
/**
|
|
4
9
|
* @concrete ../internal/types:ConfigSourceTarget
|
|
5
10
|
*/
|
|
6
11
|
export interface ConfigSource {
|
|
7
|
-
|
|
8
|
-
source: string;
|
|
9
|
-
getData(): Promise<ConfigData[] | ConfigData | undefined> | ConfigData[] | ConfigData | undefined;
|
|
12
|
+
get(): OrProm<OneOf<ConfigSpec>>;
|
|
10
13
|
}
|