-
Notifications
You must be signed in to change notification settings - Fork 333
/
Copy pathindex.ts
207 lines (184 loc) · 6.94 KB
/
index.ts
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import type { EnvVar } from './env_var.js';
export { type EnvVar } from './env_var.js';
export interface ConfigMapping {
env?: EnvVar;
parseEnv?: (val: string) => any;
defaultValue?: any;
printDefault?: (val: any) => string;
description: string;
isBoolean?: boolean;
nested?: Record<string, ConfigMapping>;
fallback?: EnvVar[];
}
export function isBooleanConfigValue<T>(obj: T, key: keyof T): boolean {
return typeof obj[key] === 'boolean';
}
export type ConfigMappingsType<T> = Record<keyof T, ConfigMapping>;
/**
* Shared utility function to get a value from environment variables with fallback support.
* This can be used by both getConfigFromMappings and CLI utilities.
*
* @param env - The primary environment variable name
* @param fallback - Optional array of fallback environment variable names
* @param parseFunc - Optional function to parse the environment variable value
* @param defaultValue - Optional default value to use if no environment variable is set
* @returns The parsed value from environment variables or the default value
*/
export function getValueFromEnvWithFallback<T>(
env: EnvVar | undefined,
parseFunc: ((val: string) => T) | undefined,
defaultValue: T | undefined,
fallback?: EnvVar[],
): T | undefined {
let value: string | undefined;
// Try primary env var
if (env) {
value = process.env[env];
}
// If primary not found, try fallbacks
if (value === undefined && fallback && fallback.length > 0) {
for (const fallbackEnv of fallback) {
const fallbackVal = process.env[fallbackEnv];
if (fallbackVal !== undefined) {
value = fallbackVal;
break;
}
}
}
// Parse the value if needed
if (value !== undefined) {
return parseFunc ? parseFunc(value) : (value as unknown as T);
}
// Return default if no env var found
return defaultValue;
}
export function getConfigFromMappings<T>(configMappings: ConfigMappingsType<T>): T {
const config = {} as T;
for (const key in configMappings) {
const { env, parseEnv, defaultValue, nested, fallback } = configMappings[key];
if (nested) {
(config as any)[key] = getConfigFromMappings(nested);
} else {
// Use the shared utility function
(config as any)[key] = getValueFromEnvWithFallback(env, parseEnv, defaultValue, fallback);
}
}
return config;
}
/**
* Filters out a service's config mappings to exclude certain keys.
* @param configMappings - The service's config mappings
* @param keysToFilter - The keys to filter out
* @returns The filtered config mappings
*/
export function omitConfigMappings<T, K extends keyof T>(
configMappings: ConfigMappingsType<T>,
keysToFilter: K[],
): ConfigMappingsType<Omit<T, K>> {
return Object.fromEntries(
Object.entries(configMappings).filter(([key]) => !keysToFilter.includes(key as K)),
) as ConfigMappingsType<Omit<T, K>>;
}
/**
* Generates parseEnv and default values for a numerical config value.
* @param defaultVal - The default numerical value to use if the environment variable is not set or is invalid
* @returns Object with parseEnv and default values for a numerical config value
*/
export function numberConfigHelper(defaultVal: number): Pick<ConfigMapping, 'parseEnv' | 'defaultValue'> {
return {
parseEnv: (val: string) => safeParseNumber(val, defaultVal),
defaultValue: defaultVal,
};
}
/**
* Generates parseEnv and default values for a numerical config value.
* @param defaultVal - The default numerical value to use if the environment variable is not set or is invalid
* @returns Object with parseEnv and default values for a numerical config value
*/
export function bigintConfigHelper(defaultVal?: bigint): Pick<ConfigMapping, 'parseEnv' | 'defaultValue'> {
return {
parseEnv: (val: string) => {
if (val === '') {
return defaultVal;
}
return BigInt(val);
},
defaultValue: defaultVal,
};
}
/**
* Generates parseEnv for an optional numerical config value.
*/
export function optionalNumberConfigHelper(): Pick<ConfigMapping, 'parseEnv'> {
return {
parseEnv: (val: string | undefined) => {
if (val !== undefined && val.length > 0) {
const parsedValue = parseInt(val);
return Number.isSafeInteger(parsedValue) ? parsedValue : undefined;
}
return undefined;
},
};
}
/**
* Generates parseEnv and default values for a boolean config value.
* @param defaultVal - The default value to use if the environment variable is not set or is invalid
* @returns Object with parseEnv and default values for a boolean config value
*/
export function booleanConfigHelper(
defaultVal = false,
): Required<Pick<ConfigMapping, 'parseEnv' | 'defaultValue' | 'isBoolean'> & { parseVal: (val: string) => boolean }> {
const parse = (val: string | boolean) => (typeof val === 'boolean' ? val : parseBooleanEnv(val));
return {
parseEnv: parse,
parseVal: parse,
defaultValue: defaultVal,
isBoolean: true,
};
}
/** Parses an env var as boolean. Returns true only if value is 1, true, or TRUE. */
export function parseBooleanEnv(val: string | undefined): boolean {
return val !== undefined && ['1', 'true', 'TRUE'].includes(val);
}
/**
* Safely parses a number from a string.
* If the value is not a number or is not a safe integer, the default value is returned.
* @param value - The string value to parse
* @param defaultValue - The default value to return
* @returns Either parsed value or default value
*/
function safeParseNumber(value: string, defaultValue: number): number {
const parsedValue = parseInt(value, 10);
return Number.isSafeInteger(parsedValue) ? parsedValue : defaultValue;
}
/**
* Picks specific keys from the given configuration mappings.
*
* @template T - The type of the full configuration object.
* @template K - The keys to pick from the configuration object.
* @param {ConfigMappingsType<T>} configMappings - The full configuration mappings object.
* @param {K[]} keys - The keys to pick from the configuration mappings.
* @returns {ConfigMappingsType<Pick<T, K>>} - A new configuration mappings object containing only the specified keys.
*/
export function pickConfigMappings<T, K extends keyof T>(
configMappings: ConfigMappingsType<T>,
keys: K[],
): ConfigMappingsType<Pick<T, K>> {
return Object.fromEntries(keys.map(key => [key, configMappings[key]])) as ConfigMappingsType<Pick<T, K>>;
}
/**
* Extracts the default configuration values from the given configuration mappings.
*
* @template T - The type of the configuration object.
* @param {ConfigMappingsType<T>} configMappings - The configuration mappings object.
* @returns {T} - The configuration object with default values.
*/
export function getDefaultConfig<T>(configMappings: ConfigMappingsType<T>): T {
const defaultConfig = {} as T;
for (const key in configMappings) {
if (configMappings[key] && configMappings[key].defaultValue !== undefined) {
(defaultConfig as any)[key] = configMappings[key].defaultValue;
}
}
return defaultConfig;
}