@memberjunction/config 4.0.0 → 4.2.0
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 +322 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# @memberjunction/config
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
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](https://github.com/cosmiconfig/cosmiconfig)), deep merge with customizable strategies, structure validation, and environment-variable parsing helpers.
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart TD
|
|
11
|
+
A["Package Defaults<br/>(hardcoded in each package)"] --> D["mergeConfigs()"]
|
|
12
|
+
B["mj.config.cjs<br/>(user overrides)"] --> D
|
|
13
|
+
C["Environment Variables"] --> E["parseBooleanEnv()"]
|
|
14
|
+
E --> F["Final Runtime Config"]
|
|
15
|
+
D --> F
|
|
16
|
+
|
|
17
|
+
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
18
|
+
style B fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
19
|
+
style C fill:#b8762f,stroke:#8a5722,color:#fff
|
|
20
|
+
style D fill:#7c5295,stroke:#563a6b,color:#fff
|
|
21
|
+
style E fill:#7c5295,stroke:#563a6b,color:#fff
|
|
22
|
+
style F fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @memberjunction/config
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If 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.
|
|
32
|
+
|
|
33
|
+
## Configuration File Discovery
|
|
34
|
+
|
|
35
|
+
When `loadMJConfig()` is called it uses cosmiconfig to walk up the directory tree looking for the first matching file in the following order:
|
|
36
|
+
|
|
37
|
+
| Priority | File / Location | Format |
|
|
38
|
+
|----------|----------------|--------|
|
|
39
|
+
| 1 | `mj.config.cjs` | CommonJS |
|
|
40
|
+
| 2 | `mj.config.js` | ESM / CommonJS |
|
|
41
|
+
| 3 | `.mjrc` | JSON / YAML |
|
|
42
|
+
| 4 | `.mjrc.js` | ESM / CommonJS |
|
|
43
|
+
| 5 | `.mjrc.cjs` | CommonJS |
|
|
44
|
+
| 6 | `"mj"` key in `package.json` | JSON |
|
|
45
|
+
|
|
46
|
+
The recommended convention across MemberJunction is `mj.config.cjs` placed at the repository root.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Loading Configuration (Async)
|
|
51
|
+
|
|
52
|
+
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.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { loadMJConfig } from '@memberjunction/config';
|
|
56
|
+
|
|
57
|
+
interface MyPackageConfig {
|
|
58
|
+
port: number;
|
|
59
|
+
debug: boolean;
|
|
60
|
+
database: {
|
|
61
|
+
host: string;
|
|
62
|
+
pool: { max: number; min: number };
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const MY_DEFAULTS: MyPackageConfig = {
|
|
67
|
+
port: 4000,
|
|
68
|
+
debug: false,
|
|
69
|
+
database: {
|
|
70
|
+
host: 'localhost',
|
|
71
|
+
pool: { max: 50, min: 5 }
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const result = await loadMJConfig<MyPackageConfig>({
|
|
76
|
+
defaultConfig: MY_DEFAULTS,
|
|
77
|
+
verbose: true // logs discovery / merge details
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
console.log(result.config); // merged configuration object
|
|
81
|
+
console.log(result.hasUserConfig); // true if mj.config.cjs was found
|
|
82
|
+
console.log(result.configFilePath); // path to the discovered file
|
|
83
|
+
console.log(result.overriddenKeys); // top-level keys the user changed
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Loading Configuration (Sync)
|
|
87
|
+
|
|
88
|
+
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.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { loadMJConfigSync } from '@memberjunction/config';
|
|
92
|
+
|
|
93
|
+
const config = loadMJConfigSync<MyPackageConfig>(
|
|
94
|
+
'/absolute/path/to/mj.config.cjs',
|
|
95
|
+
{ defaultConfig: MY_DEFAULTS }
|
|
96
|
+
);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Building a Multi-Package Configuration
|
|
100
|
+
|
|
101
|
+
`buildMJConfig` composes defaults from several MJ packages into a single configuration object before applying user overrides. This is typically called at application startup.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { buildMJConfig } from '@memberjunction/config';
|
|
105
|
+
|
|
106
|
+
const config = buildMJConfig(
|
|
107
|
+
{
|
|
108
|
+
server: serverDefaults,
|
|
109
|
+
codegen: codegenDefaults,
|
|
110
|
+
mcpServer: mcpDefaults,
|
|
111
|
+
a2aServer: a2aDefaults,
|
|
112
|
+
queryGen: queryGenDefaults
|
|
113
|
+
},
|
|
114
|
+
userOverrides // optional -- from mj.config.cjs
|
|
115
|
+
);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```mermaid
|
|
119
|
+
flowchart LR
|
|
120
|
+
S["Server<br/>Defaults"] --> B["buildMJConfig()"]
|
|
121
|
+
C["CodeGen<br/>Defaults"] --> B
|
|
122
|
+
M["MCP Server<br/>Defaults"] --> B
|
|
123
|
+
A["A2A Server<br/>Defaults"] --> B
|
|
124
|
+
Q["QueryGen<br/>Defaults"] --> B
|
|
125
|
+
U["User<br/>Overrides"] --> B
|
|
126
|
+
B --> R["Unified Config"]
|
|
127
|
+
|
|
128
|
+
style S fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
129
|
+
style C fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
130
|
+
style M fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
131
|
+
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
132
|
+
style Q fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
133
|
+
style U fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
134
|
+
style B fill:#7c5295,stroke:#563a6b,color:#fff
|
|
135
|
+
style R fill:#b8762f,stroke:#8a5722,color:#fff
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Merging Configurations Directly
|
|
139
|
+
|
|
140
|
+
`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.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { mergeConfigs } from '@memberjunction/config';
|
|
144
|
+
|
|
145
|
+
const merged = mergeConfigs(defaults, overrides, {
|
|
146
|
+
concatenateArrays: false, // true to append arrays instead of replacing
|
|
147
|
+
allowNullOverrides: false // true to let null values clear defaults
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Merge Rules
|
|
152
|
+
|
|
153
|
+
| Source Type | Behavior |
|
|
154
|
+
|-------------|----------|
|
|
155
|
+
| Primitive (string, number, boolean) | Override replaces default |
|
|
156
|
+
| Object | Deep recursive merge |
|
|
157
|
+
| Array | Override replaces default (or concatenates if `concatenateArrays: true`) |
|
|
158
|
+
| `null` / `undefined` in override | Ignored by default; replaces if `allowNullOverrides: true` |
|
|
159
|
+
| Key with `_append` suffix | Concatenates the array onto the matching base key |
|
|
160
|
+
|
|
161
|
+
The `_append` suffix is especially useful when a user wants to add items to a default array without wiping it out:
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
// mj.config.cjs
|
|
165
|
+
module.exports = {
|
|
166
|
+
// Instead of replacing excludeSchemas entirely, append to the defaults
|
|
167
|
+
excludeSchemas_append: ['staging', 'archive']
|
|
168
|
+
};
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Validating Configuration Structure
|
|
172
|
+
|
|
173
|
+
`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.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import { validateConfigStructure } from '@memberjunction/config';
|
|
177
|
+
|
|
178
|
+
const allowedKeys = new Set(['port', 'debug', 'database', 'logging']);
|
|
179
|
+
validateConfigStructure(config, allowedKeys);
|
|
180
|
+
// Warns: "Unexpected configuration keys found: databse"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Parsing Boolean Environment Variables
|
|
184
|
+
|
|
185
|
+
`parseBooleanEnv` normalizes the many string representations of boolean values commonly found in environment variables into a strict `true` / `false`.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { parseBooleanEnv } from '@memberjunction/config';
|
|
189
|
+
|
|
190
|
+
const debugMode = parseBooleanEnv(process.env.MJ_DEBUG);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Truthy values (case-insensitive): `true`, `1`, `yes`, `y`, `on`, `t`.
|
|
194
|
+
Everything else -- including `undefined`, empty string, and `null` -- returns `false`.
|
|
195
|
+
|
|
196
|
+
## API Reference
|
|
197
|
+
|
|
198
|
+
### Functions
|
|
199
|
+
|
|
200
|
+
#### `loadMJConfig<T>(options?): Promise<LoadConfigResult<T>>`
|
|
201
|
+
|
|
202
|
+
Asynchronously discovers and loads an MJ configuration file, merges it with the provided defaults, and returns the result.
|
|
203
|
+
|
|
204
|
+
#### `loadMJConfigSync<T>(configPath, options?): T`
|
|
205
|
+
|
|
206
|
+
Synchronously loads a configuration file from an explicit path and merges it with defaults. Does not search the directory tree.
|
|
207
|
+
|
|
208
|
+
#### `buildMJConfig(packageDefaults, userConfigOverrides?): Record<string, unknown>`
|
|
209
|
+
|
|
210
|
+
Merges default configurations from multiple MJ packages into a single object, then applies optional user overrides.
|
|
211
|
+
|
|
212
|
+
#### `mergeConfigs<T>(defaults, overrides, options?): T`
|
|
213
|
+
|
|
214
|
+
Deep-merges two plain objects using customizable strategies for arrays, nulls, and the `_append` suffix convention.
|
|
215
|
+
|
|
216
|
+
#### `validateConfigStructure(config, allowedKeys): void`
|
|
217
|
+
|
|
218
|
+
Logs warnings for any top-level keys in `config` that are not present in `allowedKeys`.
|
|
219
|
+
|
|
220
|
+
#### `parseBooleanEnv(value): boolean`
|
|
221
|
+
|
|
222
|
+
Parses a string (typically from `process.env`) into a boolean using common truthy conventions.
|
|
223
|
+
|
|
224
|
+
#### `isValidConfig(value): value is MJConfig`
|
|
225
|
+
|
|
226
|
+
Type guard that checks whether a value is a non-null object suitable for use as a configuration.
|
|
227
|
+
|
|
228
|
+
### Interfaces
|
|
229
|
+
|
|
230
|
+
#### `LoadConfigOptions`
|
|
231
|
+
|
|
232
|
+
| Property | Type | Default | Description |
|
|
233
|
+
|----------|------|---------|-------------|
|
|
234
|
+
| `searchFrom` | `string` | `process.cwd()` | Directory to start searching for the config file |
|
|
235
|
+
| `requireConfigFile` | `boolean` | `false` | Throw if no config file is found |
|
|
236
|
+
| `mergeOptions` | `MergeOptions` | `{}` | Controls array and null merge behavior |
|
|
237
|
+
| `verbose` | `boolean` | `false` | Log discovery and merge details to console |
|
|
238
|
+
| `defaultConfig` | `Record<string, unknown>` | `{}` | Base configuration provided by the calling package |
|
|
239
|
+
|
|
240
|
+
#### `LoadConfigResult<T>`
|
|
241
|
+
|
|
242
|
+
| Property | Type | Description |
|
|
243
|
+
|----------|------|-------------|
|
|
244
|
+
| `config` | `T` | The final merged configuration |
|
|
245
|
+
| `configFilePath` | `string \| undefined` | Path to the discovered user config file |
|
|
246
|
+
| `hasUserConfig` | `boolean` | Whether a user config file was found |
|
|
247
|
+
| `overriddenKeys` | `string[]` | Top-level keys that differ from defaults |
|
|
248
|
+
|
|
249
|
+
#### `MergeOptions`
|
|
250
|
+
|
|
251
|
+
| Property | Type | Default | Description |
|
|
252
|
+
|----------|------|---------|-------------|
|
|
253
|
+
| `concatenateArrays` | `boolean` | `false` | Append override arrays to defaults instead of replacing |
|
|
254
|
+
| `allowNullOverrides` | `boolean` | `false` | Allow `null` in overrides to clear default values |
|
|
255
|
+
|
|
256
|
+
### Types
|
|
257
|
+
|
|
258
|
+
#### `MJConfig`
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
type MJConfig = Record<string, unknown>;
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Generic configuration type. Each consuming package defines its own specific configuration interface.
|
|
265
|
+
|
|
266
|
+
## Architecture
|
|
267
|
+
|
|
268
|
+
```mermaid
|
|
269
|
+
flowchart TB
|
|
270
|
+
subgraph ConfigPackage ["@memberjunction/config"]
|
|
271
|
+
direction TB
|
|
272
|
+
CL["config-loader.ts<br/>loadMJConfig / loadMJConfigSync / buildMJConfig"]
|
|
273
|
+
CM["config-merger.ts<br/>mergeConfigs / validateConfigStructure"]
|
|
274
|
+
CT["config-types.ts<br/>MJConfig / isValidConfig"]
|
|
275
|
+
EU["env-utils.ts<br/>parseBooleanEnv"]
|
|
276
|
+
CL --> CM
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
subgraph Consumers ["Consuming Packages"]
|
|
280
|
+
direction TB
|
|
281
|
+
SRV["@memberjunction/server"]
|
|
282
|
+
CG["@memberjunction/codegen-lib"]
|
|
283
|
+
MCP["@memberjunction/mcp-server"]
|
|
284
|
+
A2A["@memberjunction/a2a-server"]
|
|
285
|
+
CLI["@memberjunction/cli"]
|
|
286
|
+
MS["@memberjunction/metadata-sync"]
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
Consumers --> ConfigPackage
|
|
290
|
+
|
|
291
|
+
style ConfigPackage fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
292
|
+
style CL fill:#7c5295,stroke:#563a6b,color:#fff
|
|
293
|
+
style CM fill:#7c5295,stroke:#563a6b,color:#fff
|
|
294
|
+
style CT fill:#7c5295,stroke:#563a6b,color:#fff
|
|
295
|
+
style EU fill:#7c5295,stroke:#563a6b,color:#fff
|
|
296
|
+
style Consumers fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
297
|
+
style SRV fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
298
|
+
style CG fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
299
|
+
style MCP fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
300
|
+
style A2A fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
301
|
+
style CLI fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
302
|
+
style MS fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Dependencies
|
|
306
|
+
|
|
307
|
+
| Package | Purpose |
|
|
308
|
+
|---------|---------|
|
|
309
|
+
| [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig) | Configuration file discovery and loading |
|
|
310
|
+
| [lodash.mergewith](https://lodash.com/docs/#mergeWith) | Deep merge with custom merge strategy |
|
|
311
|
+
| [zod](https://zod.dev/) | Schema validation (available for consumers) |
|
|
312
|
+
|
|
313
|
+
## Related Packages
|
|
314
|
+
|
|
315
|
+
| Package | Relationship |
|
|
316
|
+
|---------|-------------|
|
|
317
|
+
| `@memberjunction/server` | Defines `MJServerConfig` defaults; uses `mergeConfigs` and `parseBooleanEnv` |
|
|
318
|
+
| `@memberjunction/codegen-lib` | Defines `CodeGenConfig` defaults; uses `mergeConfigs` and `parseBooleanEnv` |
|
|
319
|
+
| `@memberjunction/mcp-server` | Defines `MCPServerConfig` defaults; uses `mergeConfigs` |
|
|
320
|
+
| `@memberjunction/a2a-server` | Defines `A2AServerConfig` defaults; uses `mergeConfigs` |
|
|
321
|
+
| `@memberjunction/cli` | CLI tool config loading; uses `mergeConfigs` and `parseBooleanEnv` |
|
|
322
|
+
| `@memberjunction/metadata-sync` | Metadata sync config; uses `mergeConfigs` and `parseBooleanEnv` |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/config",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.2.0",
|
|
5
5
|
"description": "Central configuration package for MemberJunction framework with default configurations and merge utilities",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|