@travetto/config 7.0.0-rc.1 → 7.0.0-rc.3

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 CHANGED
@@ -13,7 +13,7 @@ npm install @travetto/config
13
13
  yarn add @travetto/config
14
14
  ```
15
15
 
16
- The config module provides support for loading application config on startup. Configuration values support the common [YAML](https://en.wikipedia.org/wiki/YAML) constructs as defined in [yaml](https://github.com/eemeli/yaml). Additionally, the configuration is built upon the [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding.") module, to enforce type correctness, and allow for validation of configuration as an entrypoint into the application. Given that all [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L13) classes are [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19)-based classes, all the standard [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19) and [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L21) functionality applies.
16
+ The config module provides support for loading application config on startup. Configuration values support the common [YAML](https://en.wikipedia.org/wiki/YAML) constructs as defined in [yaml](https://github.com/eemeli/yaml). Additionally, the configuration is built upon the [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding.") module, to enforce type correctness, and allow for validation of configuration as an entrypoint into the application. Given that all [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L13) classes are [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19)-based classes, all the standard [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L19) and [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L23) functionality applies.
17
17
 
18
18
  ## Resolution
19
19
  The configuration information is comprised of:
@@ -104,27 +104,28 @@ The framework provides two simple base classes that assist with existing pattern
104
104
  **Code: Memory Provider**
105
105
  ```typescript
106
106
  import { ConfigData } from '../parser/types.ts';
107
- import { ConfigSource, ConfigSpec } from './types.ts';
107
+ import { ConfigSource, ConfigPayload } from './types.ts';
108
108
 
109
109
  /**
110
110
  * Meant to be instantiated and provided as a unique config source
111
111
  */
112
112
  export class MemoryConfigSource implements ConfigSource {
113
- #spec: ConfigSpec;
113
+ #payload: ConfigPayload;
114
114
 
115
115
  constructor(key: string, data: ConfigData, priority: number = 500) {
116
- this.#spec = { data, priority, source: `memory://${key}` };
116
+ this.#payload = { data, priority, source: `memory://${key}` };
117
117
  }
118
118
 
119
- get(): ConfigSpec {
120
- return this.#spec;
119
+ get(): ConfigPayload {
120
+ return this.#payload;
121
121
  }
122
122
  }
123
123
  ```
124
124
 
125
125
  **Code: Environment JSON Provider**
126
126
  ```typescript
127
- import { ConfigSource, ConfigSpec } from './types.ts';
127
+ import { JSONUtil } from '@travetto/runtime';
128
+ import { ConfigSource, ConfigPayload } from './types.ts';
128
129
 
129
130
  /**
130
131
  * Represents the environment mapped data as a JSON blob
@@ -138,9 +139,9 @@ export class EnvConfigSource implements ConfigSource {
138
139
  this.#priority = priority;
139
140
  }
140
141
 
141
- get(): ConfigSpec | undefined {
142
+ get(): ConfigPayload | undefined {
142
143
  try {
143
- const data = JSON.parse(process.env[this.#envKey] || '{}');
144
+ const data: Record<string, unknown> = JSONUtil.parseSafe(process.env[this.#envKey] || '{}');
144
145
  return { data, priority: this.#priority, source: `env://${this.#envKey}` };
145
146
  } catch {
146
147
  console.error(`env.${this.#envKey} is an invalid format`, { text: process.env[this.#envKey] });
@@ -154,12 +155,12 @@ In addition to files and environment variables, configuration sources can also b
154
155
 
155
156
  **Code: Custom Configuration Source**
156
157
  ```typescript
157
- import { ConfigSource, ConfigSpec } from '@travetto/config';
158
+ import { ConfigSource, ConfigPayload } from '@travetto/config';
158
159
  import { Injectable } from '@travetto/di';
159
160
 
160
161
  @Injectable()
161
162
  export class CustomConfigSource implements ConfigSource {
162
- async get(): Promise<ConfigSpec> {
163
+ async get(): Promise<ConfigPayload> {
163
164
  return {
164
165
  data: { user: { name: 'bob' } },
165
166
  source: 'custom://override',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/config",
3
- "version": "7.0.0-rc.1",
3
+ "version": "7.0.0-rc.3",
4
4
  "description": "Configuration support",
5
5
  "keywords": [
6
6
  "yaml",
@@ -26,8 +26,8 @@
26
26
  "directory": "module/config"
27
27
  },
28
28
  "dependencies": {
29
- "@travetto/di": "^7.0.0-rc.1",
30
- "@travetto/schema": "^7.0.0-rc.1",
29
+ "@travetto/di": "^7.0.0-rc.3",
30
+ "@travetto/schema": "^7.0.0-rc.3",
31
31
  "yaml": "^2.8.1"
32
32
  },
33
33
  "travetto": {
package/src/decorator.ts CHANGED
@@ -10,21 +10,21 @@ import { ConfigOverrideUtil } from './util.ts';
10
10
  * @augments `@travetto/schema:Schema`
11
11
  * @kind decorator
12
12
  */
13
- export function Config(ns: string) {
13
+ export function Config(namespace: string) {
14
14
  return <T extends Class>(cls: T): T => {
15
15
  // Declare as part of global config
16
16
  SchemaRegistryIndex.getForRegister(cls).register({ interfaces: [ConfigBaseType] });
17
17
 
18
- ConfigOverrideUtil.setOverrideConfig(cls, ns);
18
+ ConfigOverrideUtil.setOverrideConfig(cls, namespace);
19
19
 
20
20
  DependencyRegistryIndex.getForRegister(cls).registerClass();
21
21
 
22
- const og: Function = cls.prototype.postConstruct;
22
+ const handle: Function = cls.prototype.postConstruct;
23
23
  cls.prototype.postConstruct = async function (): Promise<void> {
24
24
  // Apply config
25
- const cfg = await DependencyRegistryIndex.getInstance(ConfigurationService);
26
- await cfg.bindTo(cls, this, ns);
27
- await og?.call(this);
25
+ const config = await DependencyRegistryIndex.getInstance(ConfigurationService);
26
+ await config.bindTo(cls, this, namespace);
27
+ await handle?.call(this);
28
28
  };
29
29
  return cls;
30
30
  };
@@ -1,9 +1,10 @@
1
1
  import { Injectable } from '@travetto/di';
2
+ import { JSONUtil } from '@travetto/runtime';
2
3
 
3
4
  import { ConfigParser } from './types.ts';
4
5
 
5
6
  @Injectable()
6
7
  export class JSONConfigParser implements ConfigParser {
7
8
  ext = ['.json'];
8
- parse = JSON.parse.bind(JSON);
9
+ parse = JSONUtil.parseSafe;
9
10
  }
@@ -16,7 +16,7 @@ export class ParserManager {
16
16
  const parsers = await DependencyRegistryIndex.getInstances(toConcrete<ConfigParser>());
17
17
 
18
18
  // Register parsers
19
- this.#parsers = Object.fromEntries(parsers.flatMap(p => p.ext.map(e => [e, p])));
19
+ this.#parsers = Object.fromEntries(parsers.flatMap(parser => parser.ext.map(ext => [ext, parser])));
20
20
  this.#extMatch = parsers.length ? new RegExp(`(${Object.keys(this.#parsers).join('|').replaceAll('.', '[.]')})`) : /^$/;
21
21
  }
22
22
 
package/src/service.ts CHANGED
@@ -6,11 +6,11 @@ import { BindUtil, DataUtil, SchemaRegistryIndex, SchemaValidator, ValidationRes
6
6
 
7
7
  import { ParserManager } from './parser/parser.ts';
8
8
  import { ConfigData } from './parser/types.ts';
9
- import { ConfigSource, ConfigSpec } from './source/types.ts';
9
+ import { ConfigSource, ConfigPayload } from './source/types.ts';
10
10
  import { FileConfigSource } from './source/file.ts';
11
11
  import { OverrideConfigSource } from './source/override.ts';
12
12
 
13
- type ConfigSpecSimple = Omit<ConfigSpec, 'data'>;
13
+ type ConfigSpecSimple = Omit<ConfigPayload, 'data'>;
14
14
 
15
15
  /**
16
16
  * Common Type for all configuration classes
@@ -24,15 +24,15 @@ export class ConfigBaseType { }
24
24
  export class ConfigurationService {
25
25
 
26
26
  #storage: Record<string, unknown> = {}; // Lowered, and flattened
27
- #specs: ConfigSpecSimple[] = [];
27
+ #payloads: ConfigSpecSimple[] = [];
28
28
  #secrets: (RegExp | string)[] = [/secure(-|_|[a-z])|password|private|secret|salt|(\bkey|key\b)|serviceAccount|(api(-|_)?key)/i];
29
29
 
30
30
  /**
31
31
  * Get a sub tree of the config, or everything if namespace is not passed
32
- * @param ns The namespace of the config to search for, can be dotted for accessing sub namespaces
32
+ * @param namespace The namespace of the config to search for, can be dotted for accessing sub namespaces
33
33
  */
34
- #get<T extends Record<string, unknown> = Record<string, unknown>>(ns?: string): T {
35
- const parts = (ns ? ns.split('.') : []);
34
+ #get<T extends Record<string, unknown> = Record<string, unknown>>(namespace?: string): T {
35
+ const parts = (namespace ? namespace.split('.') : []);
36
36
  let sub: Record<string, unknown> = this.#storage;
37
37
 
38
38
  while (parts.length && sub) {
@@ -53,7 +53,7 @@ export class ConfigurationService {
53
53
  const providers = await DependencyRegistryIndex.getCandidates(toConcrete<ConfigSource>());
54
54
 
55
55
  const configs = await Promise.all(
56
- providers.map(async (el) => await DependencyRegistryIndex.getInstance(el.candidateType, el.qualifier))
56
+ providers.map(async (candidate) => await DependencyRegistryIndex.getInstance<ConfigSource>(candidate.candidateType, candidate.qualifier))
57
57
  );
58
58
 
59
59
  const parser = await DependencyRegistryIndex.getInstance(ParserManager);
@@ -62,27 +62,27 @@ export class ConfigurationService {
62
62
  new FileConfigSource(parser),
63
63
  ...configs,
64
64
  new OverrideConfigSource()
65
- ].map(src => src.get()));
65
+ ].map(source => source.get()));
66
66
 
67
- const specs = possible
67
+ const payloads = possible
68
68
  .flat()
69
- .filter(x => !!x)
69
+ .filter(data => !!data)
70
70
  .toSorted((a, b) => a.priority - b.priority);
71
71
 
72
- for (const spec of specs) {
73
- DataUtil.deepAssign(this.#storage, BindUtil.expandPaths(spec.data), 'coerce');
72
+ for (const payload of payloads) {
73
+ DataUtil.deepAssign(this.#storage, BindUtil.expandPaths(payload.data), 'coerce');
74
74
  }
75
75
 
76
- this.#specs = specs.map(({ data: _, ...v }) => v);
76
+ this.#payloads = payloads.map(({ data: _, ...payload }) => payload);
77
77
 
78
78
  // Initialize Secrets
79
79
  const { secrets = [] } = this.#get<{ secrets?: string | string[] }>('config') ?? {};
80
- for (const el of [secrets].flat()) {
81
- if (typeof el === 'string') {
82
- if (el.startsWith('/')) {
83
- this.#secrets.push(DataUtil.coerceType(el, RegExp, true));
80
+ for (const value of [secrets].flat()) {
81
+ if (typeof value === 'string') {
82
+ if (value.startsWith('/')) {
83
+ this.#secrets.push(DataUtil.coerceType(value, RegExp, true));
84
84
  } else {
85
- this.#secrets.push(DataUtil.coerceType(el, String, true));
85
+ this.#secrets.push(DataUtil.coerceType(value, String, true));
86
86
  }
87
87
  }
88
88
  }
@@ -96,21 +96,21 @@ export class ConfigurationService {
96
96
  const configTargets = await DependencyRegistryIndex.getCandidates(ConfigBaseType);
97
97
  const configs = await Promise.all(
98
98
  configTargets
99
- .filter(el => el.qualifier === getDefaultQualifier(el.class)) // Is self targeting?
99
+ .filter(candidate => candidate.qualifier === getDefaultQualifier(candidate.class)) // Is self targeting?
100
100
  .toSorted((a, b) => a.class.name.localeCompare(b.class.name))
101
- .map(async el => {
102
- const inst = await DependencyRegistryIndex.getInstance(el.class, el.qualifier);
103
- return [el, inst] as const;
101
+ .map(async candidate => {
102
+ const inst = await DependencyRegistryIndex.getInstance(candidate.class, candidate.qualifier);
103
+ return [candidate, inst] as const;
104
104
  })
105
105
  );
106
106
  const out: Record<string, ConfigData> = {};
107
- for (const [el, inst] of configs) {
107
+ for (const [candidate, inst] of configs) {
108
108
  const data = BindUtil.bindSchemaToObject<ConfigData>(
109
- getClass(inst), {}, inst, { filterInput: f => !('secret' in f) || !f.secret, filterValue: v => v !== undefined }
109
+ getClass(inst), {}, inst, { filterInput: field => !('secret' in field) || !field.secret, filterValue: value => value !== undefined }
110
110
  );
111
- out[el.candidateType.name] = DataUtil.filterByKeys(data, this.#secrets);
111
+ out[candidate.candidateType.name] = DataUtil.filterByKeys(data, this.#secrets);
112
112
  }
113
- return { sources: this.#specs, active: out };
113
+ return { sources: this.#payloads, active: out };
114
114
  }
115
115
 
116
116
  /**
@@ -125,15 +125,15 @@ export class ConfigurationService {
125
125
  if (validate) {
126
126
  try {
127
127
  await SchemaValidator.validate(cls, item);
128
- } catch (err) {
129
- if (err instanceof ValidationResultError) {
130
- const ogMessage = err.message;
131
- err.message = `Failed to construct ${classId} as validation errors have occurred`;
132
- err.stack = err.stack?.replace(ogMessage, err.message);
128
+ } catch (error) {
129
+ if (error instanceof ValidationResultError) {
130
+ const originalMessage = error.message;
131
+ error.message = `Failed to construct ${classId} as validation errors have occurred`;
132
+ error.stack = error.stack?.replace(originalMessage, error.message);
133
133
  const imp = Runtime.getImport(cls);
134
- Object.defineProperty(err, 'details', { value: { class: classId, import: imp, ...(err.details ?? {}) } });
134
+ Object.defineProperty(error, 'details', { value: { class: classId, import: imp, ...(error.details ?? {}) } });
135
135
  }
136
- throw err;
136
+ throw error;
137
137
  }
138
138
  }
139
139
  return item;
@@ -152,7 +152,6 @@ export class ConfigurationService {
152
152
  env: Runtime.env,
153
153
  debug: Runtime.debug,
154
154
  production: Runtime.production,
155
- dynamic: Runtime.dynamic,
156
155
  resourcePaths: RuntimeResources.searchPaths,
157
156
  profiles: Env.TRV_PROFILES.list ?? []
158
157
  },
package/src/source/env.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { ConfigSource, ConfigSpec } from './types.ts';
1
+ import { JSONUtil } from '@travetto/runtime';
2
+ import { ConfigSource, ConfigPayload } from './types.ts';
2
3
 
3
4
  /**
4
5
  * Represents the environment mapped data as a JSON blob
@@ -12,9 +13,9 @@ export class EnvConfigSource implements ConfigSource {
12
13
  this.#priority = priority;
13
14
  }
14
15
 
15
- get(): ConfigSpec | undefined {
16
+ get(): ConfigPayload | undefined {
16
17
  try {
17
- const data = JSON.parse(process.env[this.#envKey] || '{}');
18
+ const data: Record<string, unknown> = JSONUtil.parseSafe(process.env[this.#envKey] || '{}');
18
19
  return { data, priority: this.#priority, source: `env://${this.#envKey}` };
19
20
  } catch {
20
21
  console.error(`env.${this.#envKey} is an invalid format`, { text: process.env[this.#envKey] });
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
 
4
4
  import { Env, Runtime, RuntimeResources } from '@travetto/runtime';
5
5
 
6
- import { ConfigSource, ConfigSpec } from './types.ts';
6
+ import { ConfigSource, ConfigPayload } from './types.ts';
7
7
  import { ParserManager } from '../parser/parser.ts';
8
8
 
9
9
  type Profile = [string, number] | readonly [string, number];
@@ -24,13 +24,13 @@ export class FileConfigSource implements ConfigSource {
24
24
  ['application', 100],
25
25
  [Runtime.env!, 200],
26
26
  ...(Env.TRV_PROFILES.list ?? [])
27
- .map((p, i) => [p, 300 + i * 10] as const)
28
- ] as const).filter(x => !!x[0]);
27
+ .map((profile, i) => [profile, 300 + i * 10] as const)
28
+ ] as const).filter(entry => !!entry[0]);
29
29
  }
30
30
 
31
- async get(): Promise<ConfigSpec[]> {
31
+ async get(): Promise<ConfigPayload[]> {
32
32
  const cache: Record<string, Promise<string[]>> = {};
33
- const configs: Promise<ConfigSpec>[] = [];
33
+ const configs: Promise<ConfigPayload>[] = [];
34
34
 
35
35
  for (const [profile, priority] of this.#profiles) {
36
36
  let i = priority;
@@ -1,17 +1,17 @@
1
1
  import { ConfigData } from '../parser/types.ts';
2
- import { ConfigSource, ConfigSpec } from './types.ts';
2
+ import { ConfigSource, ConfigPayload } from './types.ts';
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
- #spec: ConfigSpec;
8
+ #payload: ConfigPayload;
9
9
 
10
10
  constructor(key: string, data: ConfigData, priority: number = 500) {
11
- this.#spec = { data, priority, source: `memory://${key}` };
11
+ this.#payload = { data, priority, source: `memory://${key}` };
12
12
  }
13
13
 
14
- get(): ConfigSpec {
15
- return this.#spec;
14
+ get(): ConfigPayload {
15
+ return this.#payload;
16
16
  }
17
17
  }
@@ -1,5 +1,5 @@
1
1
  import { ConfigData } from '../parser/types.ts';
2
- import { ConfigSource, ConfigSpec } from './types.ts';
2
+ import { ConfigSource, ConfigPayload } from './types.ts';
3
3
  import { ConfigOverrideUtil } from '../util.ts';
4
4
 
5
5
  /**
@@ -11,16 +11,16 @@ export class OverrideConfigSource implements ConfigSource {
11
11
  const out: ConfigData = {};
12
12
  for (const { namespace, fields } of ConfigOverrideUtil.getAllOverrideConfigs()) {
13
13
  for (const [key, value] of Object.entries(fields)) {
14
- const val = value();
15
- if (val !== undefined && val !== '') {
16
- out[`${namespace}.${key}`] = val;
14
+ const data = value();
15
+ if (data !== undefined && data !== '') {
16
+ out[`${namespace}.${key}`] = data;
17
17
  }
18
18
  }
19
19
  }
20
20
  return out;
21
21
  }
22
22
 
23
- get(): ConfigSpec {
23
+ get(): ConfigPayload {
24
24
  return { data: this.#build(), priority: 999, source: 'memory://override' };
25
25
  }
26
26
  }
@@ -3,11 +3,11 @@ import { ConfigData } from '../parser/types.ts';
3
3
  type OrProm<T> = T | Promise<T>;
4
4
  type OneOf<T> = T[] | T | undefined;
5
5
 
6
- export type ConfigSpec = { data: ConfigData, priority: number, source: string, detail?: string };
6
+ export type ConfigPayload = { data: ConfigData, priority: number, source: string, detail?: string };
7
7
 
8
8
  /**
9
9
  * @concrete
10
10
  */
11
11
  export interface ConfigSource {
12
- get(): OrProm<OneOf<ConfigSpec>>;
12
+ get(): OrProm<OneOf<ConfigPayload>>;
13
13
  }
package/src/util.ts CHANGED
@@ -22,9 +22,9 @@ export class ConfigOverrideUtil {
22
22
  static getAllOverrideConfigs(): Required<OverrideConfig>[] {
23
23
  const out: Required<OverrideConfig>[] = [];
24
24
  for (const cls of SchemaRegistryIndex.getClasses()) {
25
- const cfg = this.getOverrideConfig(cls);
26
- if (cfg && cfg.fields && cfg.namespace) {
27
- out.push(asFull(cfg));
25
+ const config = this.getOverrideConfig(cls);
26
+ if (config && config.fields && config.namespace) {
27
+ out.push(asFull(config));
28
28
  }
29
29
  }
30
30
  return out;
@@ -35,7 +35,7 @@ export class ConfigOverrideUtil {
35
35
  .registerMetadata<OverrideConfig>(OverrideConfigSymbol, {});
36
36
 
37
37
  (env.fields ??= {})[field] = (): string | undefined =>
38
- process.env[names.find(x => !!process.env[x])!];
38
+ process.env[names.find(name => !!process.env[name])!];
39
39
  }
40
40
 
41
41
  static setOverrideConfig(cls: Class<Any>, namespace: string): void {