@travetto/config 6.0.1 → 7.0.0-rc.1

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#L13)-based classes, all the standard [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L13) and [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L25) 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#L21) functionality applies.
17
17
 
18
18
  ## Resolution
19
19
  The configuration information is comprised of:
@@ -24,8 +24,8 @@ Config loading follows a defined resolution path, below is the order in increasi
24
24
  1. `resources/application.<ext>` - Priority `100` - Load the default `application.<ext>` if available.
25
25
  1. `resources/{env}.<ext>` - Priority `200` - Load environment specific profile configurations as defined by the values of `process.env.TRV_ENV`.
26
26
  1. `resources/*.<ext>` - Priority `300` - Load profile specific configurations as defined by the values in `process.env.TRV_PROFILES`
27
- 1. [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L29) [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
28
- 1. [OverrideConfigSource](https://github.com/travetto/travetto/tree/main/module/config/src/source/override.ts#L20) - 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.
27
+ 1. [@Injectable](https://github.com/travetto/travetto/tree/main/module/di/src/decorator.ts#L15) [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
28
+ 1. [OverrideConfigSource](https://github.com/travetto/travetto/tree/main/module/config/src/source/override.ts#L9) - Priority `999` - This is for [@EnvVar](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L37) overrides, and is at the top priority for all built-in config sources.
29
29
 
30
30
  By default all configuration data is inert, and will only be applied when constructing an instance of a configuration class.
31
31
 
@@ -176,7 +176,7 @@ At startup, the [ConfigurationService](https://github.com/travetto/travetto/tree
176
176
  The [ConfigurationService](https://github.com/travetto/travetto/tree/main/module/config/src/service.ts#L24) service provides injectable access to all of the loaded configuration. For simplicity, a decorator, [@Config](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L13) allows for classes to automatically be bound with config information on post construction via the [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support.") module. The decorator will install a `postConstruct` method if not already defined, that performs the binding of configuration. This is due to the fact that we cannot rewrite the constructor, and order of operation matters.
177
177
 
178
178
  ### Environment Variables
179
- Additionally there are times in which you may want to also support configuration via environment variables. [EnvVar](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L34) supports override configuration values when environment variables are present.
179
+ Additionally there are times in which you may want to also support configuration via environment variables. [@EnvVar](https://github.com/travetto/travetto/tree/main/module/config/src/decorator.ts#L37) supports override configuration values when environment variables are present.
180
180
 
181
181
  The decorator takes in a namespace, of what part of the resolved configuration you want to bind to your class. Given the following class:
182
182
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/config",
3
- "version": "6.0.1",
3
+ "version": "7.0.0-rc.1",
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": "^6.0.1",
30
- "@travetto/schema": "^6.0.1",
29
+ "@travetto/di": "^7.0.0-rc.1",
30
+ "@travetto/schema": "^7.0.0-rc.1",
31
31
  "yaml": "^2.8.1"
32
32
  },
33
33
  "travetto": {
package/src/decorator.ts CHANGED
@@ -1,40 +1,41 @@
1
- import { Class, ClassInstance } from '@travetto/runtime';
2
- import { DependencyRegistry } from '@travetto/di';
3
- import { SchemaRegistry } from '@travetto/schema';
1
+ import { Class, ClassInstance, getClass } from '@travetto/runtime';
2
+ import { DependencyRegistryIndex } from '@travetto/di';
3
+ import { SchemaRegistryIndex } from '@travetto/schema';
4
4
 
5
- import { OverrideConfig, OverrideConfigSymbol } from './source/override.ts';
6
5
  import { ConfigurationService, ConfigBaseType } from './service.ts';
6
+ import { ConfigOverrideUtil } from './util.ts';
7
7
 
8
8
  /**
9
9
  * Indicates that the given class should be populated with the configured fields, on instantiation
10
10
  * @augments `@travetto/schema:Schema`
11
- * @augments `@travetto/di:Injectable`
11
+ * @kind decorator
12
12
  */
13
13
  export function Config(ns: string) {
14
- return <T extends Class>(target: T): T => {
15
- const og: Function = target.prototype.postConstruct;
14
+ return <T extends Class>(cls: T): T => {
16
15
  // Declare as part of global config
17
- (DependencyRegistry.getOrCreatePending(target).interfaces ??= []).push(ConfigBaseType);
18
- const env = SchemaRegistry.getOrCreatePendingMetadata<OverrideConfig>(target, OverrideConfigSymbol, { ns, fields: {} });
19
- env.ns = ns;
16
+ SchemaRegistryIndex.getForRegister(cls).register({ interfaces: [ConfigBaseType] });
20
17
 
21
- target.prototype.postConstruct = async function (): Promise<void> {
18
+ ConfigOverrideUtil.setOverrideConfig(cls, ns);
19
+
20
+ DependencyRegistryIndex.getForRegister(cls).registerClass();
21
+
22
+ const og: Function = cls.prototype.postConstruct;
23
+ cls.prototype.postConstruct = async function (): Promise<void> {
22
24
  // Apply config
23
- const cfg = await DependencyRegistry.getInstance(ConfigurationService);
24
- await cfg.bindTo(target, this, ns);
25
+ const cfg = await DependencyRegistryIndex.getInstance(ConfigurationService);
26
+ await cfg.bindTo(cls, this, ns);
25
27
  await og?.call(this);
26
28
  };
27
- return target;
29
+ return cls;
28
30
  };
29
31
  }
30
32
 
31
33
  /**
32
34
  * Allows for binding specific fields to environment variables as a top-level override
35
+ * @kind decorator
33
36
  */
34
37
  export function EnvVar(name: string, ...others: string[]) {
35
- return (inst: ClassInstance, prop: string): void => {
36
- const env = SchemaRegistry.getOrCreatePendingMetadata<OverrideConfig>(inst.constructor, OverrideConfigSymbol, { ns: '', fields: {} });
37
- env.fields[prop] = (): string | undefined =>
38
- process.env[[name, ...others].find(x => !!process.env[x])!];
38
+ return (instance: ClassInstance, property: string): void => {
39
+ ConfigOverrideUtil.setOverrideConfigField(getClass(instance), property, [name, ...others]);
39
40
  };
40
41
  }
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
 
4
- import { DependencyRegistry, Injectable } from '@travetto/di';
4
+ import { DependencyRegistryIndex, Injectable } from '@travetto/di';
5
5
  import { AppError, toConcrete } from '@travetto/runtime';
6
6
 
7
7
  import { ConfigData, ConfigParser } from './types.ts';
@@ -13,7 +13,7 @@ export class ParserManager {
13
13
  #parsers: Record<string, ConfigParser>;
14
14
 
15
15
  async postConstruct(): Promise<void> {
16
- const parsers = await DependencyRegistry.getCandidateInstances(toConcrete<ConfigParser>());
16
+ const parsers = await DependencyRegistryIndex.getInstances(toConcrete<ConfigParser>());
17
17
 
18
18
  // Register parsers
19
19
  this.#parsers = Object.fromEntries(parsers.flatMap(p => p.ext.map(e => [e, p])));
package/src/service.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import util from 'node:util';
2
2
 
3
- import { AppError, toConcrete, castTo, Class, ClassInstance, Env, Runtime, RuntimeResources } from '@travetto/runtime';
4
- import { DependencyRegistry, Injectable } from '@travetto/di';
5
- import { BindUtil, DataUtil, SchemaRegistry, SchemaValidator, ValidationResultError } from '@travetto/schema';
3
+ import { AppError, toConcrete, castTo, Class, Env, Runtime, RuntimeResources, getClass } from '@travetto/runtime';
4
+ import { DependencyRegistryIndex, getDefaultQualifier, Injectable } from '@travetto/di';
5
+ import { BindUtil, DataUtil, SchemaRegistryIndex, SchemaValidator, ValidationResultError } from '@travetto/schema';
6
6
 
7
7
  import { ParserManager } from './parser/parser.ts';
8
8
  import { ConfigData } from './parser/types.ts';
@@ -50,13 +50,13 @@ export class ConfigurationService {
50
50
  * - If of the same priority, then alpha sort on the source
51
51
  */
52
52
  async postConstruct(): Promise<void> {
53
- const providers = await DependencyRegistry.getCandidateTypes(toConcrete<ConfigSource>());
53
+ const providers = await DependencyRegistryIndex.getCandidates(toConcrete<ConfigSource>());
54
54
 
55
55
  const configs = await Promise.all(
56
- providers.map(async (el) => await DependencyRegistry.getInstance(el.class, el.qualifier))
56
+ providers.map(async (el) => await DependencyRegistryIndex.getInstance(el.candidateType, el.qualifier))
57
57
  );
58
58
 
59
- const parser = await DependencyRegistry.getInstance(ParserManager);
59
+ const parser = await DependencyRegistryIndex.getInstance(ParserManager);
60
60
 
61
61
  const possible = await Promise.all([
62
62
  new FileConfigSource(parser),
@@ -93,22 +93,22 @@ export class ConfigurationService {
93
93
  * - Will not show fields marked as secret
94
94
  */
95
95
  async exportActive(): Promise<{ sources: ConfigSpecSimple[], active: ConfigData }> {
96
- const configTargets = await DependencyRegistry.getCandidateTypes(ConfigBaseType);
96
+ const configTargets = await DependencyRegistryIndex.getCandidates(ConfigBaseType);
97
97
  const configs = await Promise.all(
98
98
  configTargets
99
- .filter(el => el.qualifier === DependencyRegistry.get(el.class).qualifier) // Is primary?
99
+ .filter(el => el.qualifier === getDefaultQualifier(el.class)) // Is self targeting?
100
100
  .toSorted((a, b) => a.class.name.localeCompare(b.class.name))
101
101
  .map(async el => {
102
- const inst = await DependencyRegistry.getInstance<ClassInstance>(el.class, el.qualifier);
102
+ const inst = await DependencyRegistryIndex.getInstance(el.class, el.qualifier);
103
103
  return [el, inst] as const;
104
104
  })
105
105
  );
106
106
  const out: Record<string, ConfigData> = {};
107
107
  for (const [el, inst] of configs) {
108
108
  const data = BindUtil.bindSchemaToObject<ConfigData>(
109
- inst.constructor, {}, inst, { filterField: f => !f.secret, filterValue: v => v !== undefined }
109
+ getClass(inst), {}, inst, { filterInput: f => !('secret' in f) || !f.secret, filterValue: v => v !== undefined }
110
110
  );
111
- out[el.class.name] = DataUtil.filterByKeys(data, this.#secrets);
111
+ out[el.candidateType.name] = DataUtil.filterByKeys(data, this.#secrets);
112
112
  }
113
113
  return { sources: this.#specs, active: out };
114
114
  }
@@ -118,7 +118,7 @@ export class ConfigurationService {
118
118
  */
119
119
  async bindTo<T>(cls: Class<T>, item: T, namespace: string, validate = true): Promise<T> {
120
120
  const classId = cls.Ⲑid;
121
- if (!SchemaRegistry.has(cls)) {
121
+ if (!SchemaRegistryIndex.has(cls)) {
122
122
  throw new AppError(`${classId} is not a valid schema class, config is not supported`);
123
123
  }
124
124
  BindUtil.bindSchemaToObject(cls, item, this.#get(namespace));
@@ -1,17 +1,6 @@
1
- import { SchemaRegistry } from '@travetto/schema';
2
-
3
1
  import { ConfigData } from '../parser/types.ts';
4
2
  import { ConfigSource, ConfigSpec } from './types.ts';
5
-
6
- export const OverrideConfigSymbol = Symbol.for('@travetto/config:overrides');
7
-
8
- /**
9
- * Configuration Override
10
- */
11
- export type OverrideConfig = {
12
- ns: string;
13
- fields: Record<string, () => (unknown | undefined)>;
14
- };
3
+ import { ConfigOverrideUtil } from '../util.ts';
15
4
 
16
5
  /**
17
6
  * Overridable config source, provides ability to override field level values, currently used by
@@ -20,12 +9,11 @@ export type OverrideConfig = {
20
9
  export class OverrideConfigSource implements ConfigSource {
21
10
  #build(): ConfigData {
22
11
  const out: ConfigData = {};
23
- for (const cls of SchemaRegistry.getClasses()) {
24
- const { ns, fields = {} } = SchemaRegistry.getMetadata<OverrideConfig>(cls, OverrideConfigSymbol) ?? {};
12
+ for (const { namespace, fields } of ConfigOverrideUtil.getAllOverrideConfigs()) {
25
13
  for (const [key, value] of Object.entries(fields)) {
26
14
  const val = value();
27
15
  if (val !== undefined && val !== '') {
28
- out[`${ns}.${key}`] = val;
16
+ out[`${namespace}.${key}`] = val;
29
17
  }
30
18
  }
31
19
  }
package/src/util.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { Any, asFull, Class } from '@travetto/runtime';
2
+ import { SchemaRegistryIndex } from '@travetto/schema';
3
+
4
+ export const OverrideConfigSymbol = Symbol.for('@travetto/config:overrides');
5
+
6
+ /**
7
+ * Configuration Override
8
+ */
9
+ export type OverrideConfig = {
10
+ namespace?: string;
11
+ fields?: Record<string, () => (unknown | undefined)>;
12
+ };
13
+
14
+ /**
15
+ * Utility for managing override configuration via SchemaRegistryIndex
16
+ */
17
+ export class ConfigOverrideUtil {
18
+ static getOverrideConfig(cls: Class<Any>): OverrideConfig | undefined {
19
+ return SchemaRegistryIndex.get(cls).getMetadata<OverrideConfig>(OverrideConfigSymbol);
20
+ }
21
+
22
+ static getAllOverrideConfigs(): Required<OverrideConfig>[] {
23
+ const out: Required<OverrideConfig>[] = [];
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));
28
+ }
29
+ }
30
+ return out;
31
+ }
32
+
33
+ static setOverrideConfigField(cls: Class<Any>, field: string, names: string[]): void {
34
+ const env = SchemaRegistryIndex.getForRegister(cls)
35
+ .registerMetadata<OverrideConfig>(OverrideConfigSymbol, {});
36
+
37
+ (env.fields ??= {})[field] = (): string | undefined =>
38
+ process.env[names.find(x => !!process.env[x])!];
39
+ }
40
+
41
+ static setOverrideConfig(cls: Class<Any>, namespace: string): void {
42
+ SchemaRegistryIndex.getForRegister(cls).registerMetadata<OverrideConfig>(OverrideConfigSymbol, { namespace });
43
+ }
44
+ }