New Feature: dynamic_config

The ApplicationModuleMetadata interface has been updated with a new configuration parameter: dynamic_config.

This parameter takes the format of a ValueProvider | FactoryProvider, which can pull in other providers in order to generate configuration data.

The value merge priorities have been updated to the following:

  1. values provided via module definitions
  2. values provided via bootstrap
  3. values loaded via dynamic config
  4. values loaded from configuration files
  5. values loaded from environment variables
  6. values loaded from command line switches

Use Case: Centralized configuration provider

At the beginning of the bootstrapping sequence, the application perform a http request to a central configuration provider in order to retrieve additional auth keys. These keys could then be injected / refined via the normal @InjectConfig mechanisms

Gotchas

This value is resolved at the extreme start of bootstrapping. Care needs to be taken to not create chicken-and-egg situations. It is best for loaders to be extremely lean in their dependences

Example

loader.ts

import { AbstractConfig, AutoLogService, FetchService } from "@digital-alchemy/boilerplate";
import { is, SECOND, sleep, START } from "@digital-alchemy/utilities";
import { hostname, userInfo } from "os";
import { exit } from "process";

type SystemInitResponse = { config: AbstractConfig };
const { username } = userInfo();
const name = hostname();

const ATTEMPTS = 10;

export const CONFIG_LOADER = (app: string) => ({
  inject: [FetchService, AutoLogService],
  async useFactory(fetch: FetchService, logger: AutoLogService) {
    for (let i = START; i <= ATTEMPTS; i++) {
      const configuration = await fetch.fetch<SystemInitResponse>({
        rawUrl: true,
        url: `http://central.control/init/identify/${username}/${name}/${app}`,
      });
      if (is.object(configuration)) {
        return configuration.config;
      }
      logger.warn(`Failed to load configuration. Attempt [%s]/[%s]`, i, ATTEMPTS);
      await sleep(SECOND);
    }
    logger.fatal(`FAILED TO LOAD CONFIGURATION`);
    exit();
  },
});

app.ts

import { ApplicationModule } from "@digital-alchemy/boilerplate";

import { CONFIG_LOADER } from "./loader"


@ApplicationModule({
   dynamic_config: CONFIG_LOADER("special-app"),
 })
 export class MyApplication {}