@memberjunction/config
Central configuration loading and merging utilities for the MemberJunction framework. This package provides a standardized way for MJ packages to define default configurations, discover user override files, and merge them together with a deterministic precedence order.
Overview
Section titled “Overview”MemberJunction applications are configured through a layered system where each package defines its own defaults and users supply overrides through a shared mj.config.cjs file. This package provides the infrastructure that makes that layering work: config file discovery (via cosmiconfig), deep merge with customizable strategies, structure validation, and environment-variable parsing helpers.
flowchart TD
A["Package Defaults<br/>(hardcoded in each package)"] --> D["mergeConfigs()"]
B["mj.config.cjs<br/>(user overrides)"] --> D
C["Environment Variables"] --> E["parseBooleanEnv()"]
E --> F["Final Runtime Config"]
D --> F
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
style B fill:#2d8659,stroke:#1a5c3a,color:#fff
style C fill:#b8762f,stroke:#8a5722,color:#fff
style D fill:#7c5295,stroke:#563a6b,color:#fff
style E fill:#7c5295,stroke:#563a6b,color:#fff
style F fill:#2d6a9f,stroke:#1a4971,color:#fff
Installation
Section titled “Installation”npm install @memberjunction/configIf you are working inside the MemberJunction monorepo, add the dependency to the consuming package’s package.json and run npm install at the repository root.
Configuration File Discovery
Section titled “Configuration File Discovery”When loadMJConfig() is called it uses cosmiconfig to walk up the directory tree looking for the first matching file in the following order:
| Priority | File / Location | Format |
|---|---|---|
| 1 | mj.config.cjs | CommonJS |
| 2 | mj.config.js | ESM / CommonJS |
| 3 | .mjrc | JSON / YAML |
| 4 | .mjrc.js | ESM / CommonJS |
| 5 | .mjrc.cjs | CommonJS |
| 6 | "mj" key in package.json | JSON |
The recommended convention across MemberJunction is mj.config.cjs placed at the repository root.
Loading Configuration (Async)
Section titled “Loading Configuration (Async)”The primary entry point. It discovers the user’s config file, merges it with the package defaults, and returns the result along with metadata about what was loaded.
import { loadMJConfig } from '@memberjunction/config';
interface MyPackageConfig { port: number; debug: boolean; database: { host: string; pool: { max: number; min: number }; };}
const MY_DEFAULTS: MyPackageConfig = { port: 4000, debug: false, database: { host: 'localhost', pool: { max: 50, min: 5 } }};
const result = await loadMJConfig<MyPackageConfig>({ defaultConfig: MY_DEFAULTS, verbose: true // logs discovery / merge details});
console.log(result.config); // merged configuration objectconsole.log(result.hasUserConfig); // true if mj.config.cjs was foundconsole.log(result.configFilePath); // path to the discovered fileconsole.log(result.overriddenKeys); // top-level keys the user changedLoading Configuration (Sync)
Section titled “Loading Configuration (Sync)”For cases where an async call is not possible (CommonJS bootstrap code, for example), loadMJConfigSync accepts an explicit file path instead of searching the directory tree.
import { loadMJConfigSync } from '@memberjunction/config';
const config = loadMJConfigSync<MyPackageConfig>( '/absolute/path/to/mj.config.cjs', { defaultConfig: MY_DEFAULTS });Building a Multi-Package Configuration
Section titled “Building a Multi-Package Configuration”buildMJConfig composes defaults from several MJ packages into a single configuration object before applying user overrides. This is typically called at application startup.
import { buildMJConfig } from '@memberjunction/config';
const config = buildMJConfig( { server: serverDefaults, codegen: codegenDefaults, mcpServer: mcpDefaults, a2aServer: a2aDefaults, queryGen: queryGenDefaults }, userOverrides // optional -- from mj.config.cjs);flowchart LR
S["Server<br/>Defaults"] --> B["buildMJConfig()"]
C["CodeGen<br/>Defaults"] --> B
M["MCP Server<br/>Defaults"] --> B
A["A2A Server<br/>Defaults"] --> B
Q["QueryGen<br/>Defaults"] --> B
U["User<br/>Overrides"] --> B
B --> R["Unified Config"]
style S fill:#2d6a9f,stroke:#1a4971,color:#fff
style C fill:#2d6a9f,stroke:#1a4971,color:#fff
style M fill:#2d6a9f,stroke:#1a4971,color:#fff
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
style Q fill:#2d6a9f,stroke:#1a4971,color:#fff
style U fill:#2d8659,stroke:#1a5c3a,color:#fff
style B fill:#7c5295,stroke:#563a6b,color:#fff
style R fill:#b8762f,stroke:#8a5722,color:#fff
Merging Configurations Directly
Section titled “Merging Configurations Directly”mergeConfigs performs a deep merge of two plain objects. It is used internally by the loader functions and is also exported for packages that need to merge configuration fragments on their own.
import { mergeConfigs } from '@memberjunction/config';
const merged = mergeConfigs(defaults, overrides, { concatenateArrays: false, // true to append arrays instead of replacing allowNullOverrides: false // true to let null values clear defaults});Merge Rules
Section titled “Merge Rules”| Source Type | Behavior |
|---|---|
| Primitive (string, number, boolean) | Override replaces default |
| Object | Deep recursive merge |
| Array | Override replaces default (or concatenates if concatenateArrays: true) |
null / undefined in override | Ignored by default; replaces if allowNullOverrides: true |
Key with _append suffix | Concatenates the array onto the matching base key |
The _append suffix is especially useful when a user wants to add items to a default array without wiping it out:
module.exports = { // Instead of replacing excludeSchemas entirely, append to the defaults excludeSchemas_append: ['staging', 'archive']};Validating Configuration Structure
Section titled “Validating Configuration Structure”validateConfigStructure checks a merged configuration object against a set of expected top-level keys and logs warnings for any unexpected entries. This helps catch typos and deprecated settings early.
import { validateConfigStructure } from '@memberjunction/config';
const allowedKeys = new Set(['port', 'debug', 'database', 'logging']);validateConfigStructure(config, allowedKeys);// Warns: "Unexpected configuration keys found: databse"Parsing Boolean Environment Variables
Section titled “Parsing Boolean Environment Variables”parseBooleanEnv normalizes the many string representations of boolean values commonly found in environment variables into a strict true / false.
import { parseBooleanEnv } from '@memberjunction/config';
const debugMode = parseBooleanEnv(process.env.MJ_DEBUG);Truthy values (case-insensitive): true, 1, yes, y, on, t.
Everything else — including undefined, empty string, and null — returns false.
API Reference
Section titled “API Reference”Functions
Section titled “Functions”loadMJConfig<T>(options?): Promise<LoadConfigResult<T>>
Section titled “loadMJConfig<T>(options?): Promise<LoadConfigResult<T>>”Asynchronously discovers and loads an MJ configuration file, merges it with the provided defaults, and returns the result.
loadMJConfigSync<T>(configPath, options?): T
Section titled “loadMJConfigSync<T>(configPath, options?): T”Synchronously loads a configuration file from an explicit path and merges it with defaults. Does not search the directory tree.
buildMJConfig(packageDefaults, userConfigOverrides?): Record<string, unknown>
Section titled “buildMJConfig(packageDefaults, userConfigOverrides?): Record<string, unknown>”Merges default configurations from multiple MJ packages into a single object, then applies optional user overrides.
mergeConfigs<T>(defaults, overrides, options?): T
Section titled “mergeConfigs<T>(defaults, overrides, options?): T”Deep-merges two plain objects using customizable strategies for arrays, nulls, and the _append suffix convention.
validateConfigStructure(config, allowedKeys): void
Section titled “validateConfigStructure(config, allowedKeys): void”Logs warnings for any top-level keys in config that are not present in allowedKeys.
parseBooleanEnv(value): boolean
Section titled “parseBooleanEnv(value): boolean”Parses a string (typically from process.env) into a boolean using common truthy conventions.
isValidConfig(value): value is MJConfig
Section titled “isValidConfig(value): value is MJConfig”Type guard that checks whether a value is a non-null object suitable for use as a configuration.
Interfaces
Section titled “Interfaces”LoadConfigOptions
Section titled “LoadConfigOptions”| Property | Type | Default | Description |
|---|---|---|---|
searchFrom | string | process.cwd() | Directory to start searching for the config file |
requireConfigFile | boolean | false | Throw if no config file is found |
mergeOptions | MergeOptions | {} | Controls array and null merge behavior |
verbose | boolean | false | Log discovery and merge details to console |
defaultConfig | Record<string, unknown> | {} | Base configuration provided by the calling package |
LoadConfigResult<T>
Section titled “LoadConfigResult<T>”| Property | Type | Description |
|---|---|---|
config | T | The final merged configuration |
configFilePath | string | undefined | Path to the discovered user config file |
hasUserConfig | boolean | Whether a user config file was found |
overriddenKeys | string[] | Top-level keys that differ from defaults |
MergeOptions
Section titled “MergeOptions”| Property | Type | Default | Description |
|---|---|---|---|
concatenateArrays | boolean | false | Append override arrays to defaults instead of replacing |
allowNullOverrides | boolean | false | Allow null in overrides to clear default values |
MJConfig
Section titled “MJConfig”type MJConfig = Record<string, unknown>;Generic configuration type. Each consuming package defines its own specific configuration interface.
Architecture
Section titled “Architecture”flowchart TB
subgraph ConfigPackage ["@memberjunction/config"]
direction TB
CL["config-loader.ts<br/>loadMJConfig / loadMJConfigSync / buildMJConfig"]
CM["config-merger.ts<br/>mergeConfigs / validateConfigStructure"]
CT["config-types.ts<br/>MJConfig / isValidConfig"]
EU["env-utils.ts<br/>parseBooleanEnv"]
CL --> CM
end
subgraph Consumers ["Consuming Packages"]
direction TB
SRV["@memberjunction/server"]
CG["@memberjunction/codegen-lib"]
MCP["@memberjunction/mcp-server"]
A2A["@memberjunction/a2a-server"]
CLI["@memberjunction/cli"]
MS["@memberjunction/metadata-sync"]
end
Consumers --> ConfigPackage
style ConfigPackage fill:#2d6a9f,stroke:#1a4971,color:#fff
style CL fill:#7c5295,stroke:#563a6b,color:#fff
style CM fill:#7c5295,stroke:#563a6b,color:#fff
style CT fill:#7c5295,stroke:#563a6b,color:#fff
style EU fill:#7c5295,stroke:#563a6b,color:#fff
style Consumers fill:#2d8659,stroke:#1a5c3a,color:#fff
style SRV fill:#2d8659,stroke:#1a5c3a,color:#fff
style CG fill:#2d8659,stroke:#1a5c3a,color:#fff
style MCP fill:#2d8659,stroke:#1a5c3a,color:#fff
style A2A fill:#2d8659,stroke:#1a5c3a,color:#fff
style CLI fill:#2d8659,stroke:#1a5c3a,color:#fff
style MS fill:#2d8659,stroke:#1a5c3a,color:#fff
Dependencies
Section titled “Dependencies”| Package | Purpose |
|---|---|
| cosmiconfig | Configuration file discovery and loading |
| lodash.mergewith | Deep merge with custom merge strategy |
| zod | Schema validation (available for consumers) |
Related Packages
Section titled “Related Packages”| Package | Relationship |
|---|---|
@memberjunction/server | Defines MJServerConfig defaults; uses mergeConfigs and parseBooleanEnv |
@memberjunction/codegen-lib | Defines CodeGenConfig defaults; uses mergeConfigs and parseBooleanEnv |
@memberjunction/mcp-server | Defines MCPServerConfig defaults; uses mergeConfigs |
@memberjunction/a2a-server | Defines A2AServerConfig defaults; uses mergeConfigs |
@memberjunction/cli | CLI tool config loading; uses mergeConfigs and parseBooleanEnv |
@memberjunction/metadata-sync | Metadata sync config; uses mergeConfigs and parseBooleanEnv |