All files / src/core resolve.ts

100% Statements 15/15
100% Branches 8/8
100% Functions 1/1
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 729x 9x                                                                                     9x 28x 28x             28x 4x     24x 18x     6x   6x 6x 5x       6x    
import { ConfigurationRegistry } from '@/core/ConfigurationRegistry'
import { ConfigurationError } from '@/exceptions/ConfigurationError'
 
/**
 * @function
 *
 * Resolves a module's configuration.
 *
 * The one way the rest of the system reads configuration. Callers get a settled
 * object and never learn whether a value was defaulted or overridden.
 *
 * Merging is per field, not per module: an override naming only `model` keeps
 * the default `binding`, so a partial override cannot blank the rest. A field
 * set to `undefined` is ignored for the same reason — `{ model: undefined }`
 * means "no opinion", not "erase it".
 *
 * @remarks
 * The surface is registered by the application, through {@link configure}.
 * Keeping that edge in one place is what lets business logic stay unaware of
 * where settings come from.
 *
 * The type argument is explicit because this package cannot know the assembled
 * surface. The caller declares the shape it expects; the application is
 * responsible for having registered something that matches.
 *
 * @example
 * ```ts
 * const settings = resolve<CacheSettings>('cache')
 * // { ttl: 300 }
 * ```
 *
 * @typeParam S The settings shape the caller expects back.
 *
 * @param module Name of the module to resolve.
 * @returns The module's settings, with any override applied over the default.
 *
 * @throws {@link ConfigurationError} When the module is registered in neither
 * the defaults nor the overrides — almost always because `configure()` has not
 * run yet, or because a second copy of this package is loaded.
 *
 * @author Bayu Dwiyan Satria
 * @version 1.0.0
 * @since 1.0.0
 */
export const resolve = <S = unknown>(module: string): S => {
  const base = ConfigurationRegistry.defaults[module] as Record<string, unknown> | undefined
  const override = ConfigurationRegistry.overrides[module] as Record<string, unknown> | undefined
 
  /*
   * Throw rather than return undefined. Otherwise the fault surfaces at
   * whichever binding first dereferences the settings, where the message names
   * the cache instead of the missing configure() call behind it.
   */
  if (base === undefined && override === undefined) {
    throw new ConfigurationError(module)
  }
 
  if (!override) {
    return base as S
  }
 
  const resolved: Record<string, unknown> = { ...base }
 
  for (const [key, value] of Object.entries(override)) {
    if (value !== undefined) {
      resolved[key] = value
    }
  }
 
  return resolved as S
}